Resolve merge conflicts
commit
8d9a47b93e
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [".js", ".jsx", ".ts", ".tsx"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": ["airbnb-base"],
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"linebreak-style": "off",
|
||||
"no-unused-vars": "off",
|
||||
"max-len": "off",
|
||||
"import/no-dynamic-require": "off",
|
||||
"global-require": "off",
|
||||
"class-methods-use-this": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"camelcase": "off",
|
||||
"indent": "warn",
|
||||
"object-curly-newline": "off",
|
||||
"import/prefer-default-export": "off",
|
||||
"no-useless-constructor": "off",
|
||||
"@typescript-eslint/no-useless-constructor": 2,
|
||||
"import/extensions": "off",
|
||||
"no-param-reassign": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"keyword-spacing": "off",
|
||||
"no-multiple-empty-lines": "off",
|
||||
"consistent-return": "off",
|
||||
"no-continue": "off"
|
||||
},
|
||||
"ignorePatterns": "**/*.js"
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
# Package Management & Libraries
|
||||
node_modules
|
||||
|
||||
# Configuration Files
|
||||
config.yaml
|
||||
configs/config.yaml
|
||||
src/config.yaml
|
||||
build/config.yaml
|
||||
.vscode
|
||||
yarn-error.log
|
||||
google.json
|
||||
src/google.json
|
||||
build/google.json
|
||||
|
||||
# Build/Distribution Files
|
||||
build
|
||||
dist
|
||||
|
||||
# Storage/DB Files
|
||||
localstorage
|
|
@ -0,0 +1,23 @@
|
|||
stages:
|
||||
- lint
|
||||
- build
|
||||
|
||||
lint:
|
||||
stage: lint
|
||||
script: |
|
||||
yarn install
|
||||
yarn lint
|
||||
only:
|
||||
- pushes
|
||||
- merge_requests
|
||||
- web
|
||||
|
||||
tsc:
|
||||
stage: build
|
||||
script: |
|
||||
yarn install
|
||||
tsc -p tsconfig.json -noEmit
|
||||
only:
|
||||
- pushes
|
||||
- merge_requests
|
||||
- web
|
|
@ -0,0 +1,13 @@
|
|||
# BUG REPORT
|
||||
|
||||
**Brief Description:**
|
||||
|
||||
**Priority:** (1-5, 5 being the most urgent)
|
||||
|
||||
**Steps to Reproduce:** (separated with numbers)
|
||||
|
||||
**Expected Result:**
|
||||
|
||||
**Actual Result:**
|
||||
|
||||
**Notes:** (delete if none)
|
|
@ -0,0 +1,5 @@
|
|||
## FEATURE REQUEST / SUGGESTION
|
||||
|
||||
**Request:**
|
||||
|
||||
**Description:**
|
|
@ -0,0 +1,16 @@
|
|||
# CONTRIBUTIONS
|
||||
|
||||
We accept contributions from the community, however there's a few steps you need to do first before you're able to fork the project.
|
||||
|
||||
1. Join the [Discord Server](https://loc.sh/discord).
|
||||
2. Send a DM to @Ramirez in the server, provide your GitLab username.
|
||||
3. We'll let you know when you'll be able to fork the project.
|
||||
|
||||
|
||||
## Issues
|
||||
If you're interested in tackling an issue, please comment on that particular issue that you're handling it so Maintainers can label it appropriately.
|
||||
|
||||
## Other Information
|
||||
* Make sure your contributions match the current style of the code, run `yarn run lint` to find issues with the style. Requests will be denied if they do not comply with styling.
|
||||
* Submit your merge requests to the **dev** branch only.
|
||||
* If you can use TypeScript functionality, do it. For example, don't declare something as `any` if it can be typed.
|
|
@ -0,0 +1,661 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
Community Relations
|
||||
Copyright (C) 2020 Engineering Management
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,10 @@
|
|||
all: clean build
|
||||
|
||||
clean:
|
||||
@-rm -rf build
|
||||
|
||||
build:
|
||||
-npx tsc -p ./tsconfig.json
|
||||
|
||||
run:
|
||||
cd build && node main
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"name": "loccr",
|
||||
"version": "1.0.0",
|
||||
"description": "The official system for handling Community Relations in the LOC Discord server.",
|
||||
"main": "build/main.js",
|
||||
"scripts": {
|
||||
"lint": "eslint -c ./.eslintrc.json src --ext ts"
|
||||
},
|
||||
"repository": "https://gitlab.libraryofcode.org/engineering/communityrelations.git",
|
||||
"author": "Matthew R, AD, FSEN <matthew@staff.libraryofcode.org>",
|
||||
"license": "AGPL-3.0",
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
"@types/ari-client": "^2.2.2",
|
||||
"@types/bull": "^3.14.4",
|
||||
"@types/cron": "^1.7.2",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/helmet": "^0.0.47",
|
||||
"@types/jsonwebtoken": "^8.5.0",
|
||||
"@types/mathjs": "^6.0.7",
|
||||
"@types/mongoose": "^5.7.19",
|
||||
"@types/node": "^14.14.25",
|
||||
"@types/nodemailer": "^6.4.0",
|
||||
"@types/puppeteer": "^5.4.3",
|
||||
"@types/signale": "^1.4.1",
|
||||
"@types/uuid": "^7.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||
"@typescript-eslint/parser": "^2.33.0",
|
||||
"eslint": "^7.19.0",
|
||||
"eslint-config-airbnb-base": "^14.1.0",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"tslib": "^2.1.0",
|
||||
"typescript": "^3.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google-cloud/text-to-speech": "^3.1.2",
|
||||
"ari-client": "^2.2.0",
|
||||
"asterisk-manager": "^0.1.16",
|
||||
"awesome-phonenumber": "^2.45.0",
|
||||
"axios": "^0.19.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"brain.js": "^2.0.0-beta.2",
|
||||
"bull": "^3.20.1",
|
||||
"cheerio": "^1.0.0-rc.5",
|
||||
"cron": "^1.8.2",
|
||||
"eris": "^0.14.0",
|
||||
"eris-pagination": "github:bsian03/eris-pagination",
|
||||
"express": "^4.17.1",
|
||||
"helmet": "^3.22.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mathjs": "^7.6.0",
|
||||
"moment": "^2.25.3",
|
||||
"mongoose": "^5.11.15",
|
||||
"nodemailer": "^6.4.8",
|
||||
"pluris": "^0.2.5",
|
||||
"puppeteer": "^5.5.0",
|
||||
"sd-notify": "^2.8.0",
|
||||
"signale": "^1.4.0",
|
||||
"stock-info": "^1.2.0",
|
||||
"stripe": "^8.120.0",
|
||||
"uuid": "^8.0.0",
|
||||
"yaml": "^1.9.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import { Server, ServerManagement } from '../../class';
|
||||
|
||||
export default (management: ServerManagement) => new Server(management, 3892, `${__dirname}/routes`);
|
|
@ -0,0 +1 @@
|
|||
export { default as Root } from './root';
|
|
@ -0,0 +1,920 @@
|
|||
import { Guild, GuildTextableChannel, Member, TextChannel } from 'eris';
|
||||
import { v4 as genUUID } from 'uuid';
|
||||
import { Request } from 'express';
|
||||
import { RichEmbed, Route, Server } from '../../../class';
|
||||
import { StaffInterface } from '../../../models';
|
||||
|
||||
export default class Root extends Route {
|
||||
constructor(server: Server) {
|
||||
super(server);
|
||||
this.conf = {
|
||||
path: '/api',
|
||||
};
|
||||
|
||||
this.server.client.once('ready', async () => {
|
||||
this.guild = this.server.client.guilds.get(this.server.client.config.guildID) || await this.server.client.getRESTGuild(this.server.client.config.guildID);
|
||||
this.directorRole = '662163685439045632';
|
||||
this.directorLogs = <TextChannel>this.guild.channels.get('807444198969835550');
|
||||
this.chairman = '278620217221971968';
|
||||
});
|
||||
}
|
||||
|
||||
private guild: Guild;
|
||||
|
||||
private directorRole: string;
|
||||
|
||||
private directorLogs: GuildTextableChannel;
|
||||
|
||||
private chairman: string;
|
||||
|
||||
private async authenticate(req: Request) {
|
||||
if (!req.headers.authorization) return false;
|
||||
|
||||
const users = await this.server.client.db.Score.find();
|
||||
const director = users.find((user) => user.pin.join('-') === req.headers.authorization);
|
||||
if (!director) return false;
|
||||
|
||||
const member = this.guild.members.get(director.userID);
|
||||
if (!member) return false;
|
||||
if (!member.roles.includes(this.directorRole)) return false;
|
||||
|
||||
const staffProfile = await this.server.client.db.Staff.findOne({ userID: member.id });
|
||||
|
||||
return { member, director: staffProfile };
|
||||
}
|
||||
|
||||
private genEmbed(
|
||||
id: string,
|
||||
type: 'eo' | 'motion' | 'proc' | 'res' | 'confirmMotion',
|
||||
director: {
|
||||
user: StaffInterface,
|
||||
member: Member
|
||||
},
|
||||
payload: {
|
||||
subject: string,
|
||||
body: string,
|
||||
},
|
||||
) {
|
||||
let title: string;
|
||||
let color: number;
|
||||
|
||||
switch (type) {
|
||||
case 'eo':
|
||||
title = 'Executive Order';
|
||||
color = 0xff00a7;
|
||||
break;
|
||||
|
||||
case 'motion':
|
||||
title = 'Motion';
|
||||
color = 0xffbb5f;
|
||||
break;
|
||||
|
||||
case 'proc':
|
||||
title = 'Proclamation';
|
||||
color = 0x9b89ff;
|
||||
break;
|
||||
|
||||
case 'res':
|
||||
title = 'Resolution';
|
||||
color = 0x27b17a;
|
||||
break;
|
||||
|
||||
case 'confirmMotion':
|
||||
title = 'Motion Confirmed';
|
||||
color = 0x4a6cc5;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new TypeError('You\'ve specified an invalid type for this action log. Valid types: "eo", "motion", "proc" and "res"');
|
||||
}
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle(title);
|
||||
embed.setAuthor(`${director.member.username}#${director.member.discriminator}, ${director.user.pn.join(', ')}`, director.member.avatarURL);
|
||||
embed.setDescription(`${id}\n\n_This action is available on the Board Register System Directory. You can make changes or edit it [here](https://board.ins/directory?id=${id})._`);
|
||||
embed.addField('Subject', payload.subject);
|
||||
embed.addField('Body', payload.body);
|
||||
embed.setColor(color);
|
||||
embed.setFooter('Library of Code sp-us | Board Register System', 'https://static.libraryofcode.org/library_of_code.png');
|
||||
embed.setTimestamp();
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
||||
public bind() {
|
||||
this.router.all('/', (_req, res, next) => {
|
||||
res.setHeader('Control-Allow-Access-Origin', '*');
|
||||
next();
|
||||
});
|
||||
|
||||
this.router.get('/', async (_req, res) => {
|
||||
const eo = await this.server.client.db.ExecutiveOrder.countDocuments();
|
||||
const motion = await this.server.client.db.Motion.countDocuments();
|
||||
const proc = await this.server.client.db.Proclamation.countDocuments();
|
||||
const resolution = await this.server.client.db.Proclamation.countDocuments();
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
counts: {
|
||||
eo,
|
||||
motion,
|
||||
proc,
|
||||
resolution,
|
||||
total: eo + motion + proc + resolution,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
this.router.get('/identify', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).send({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: `You have authenticated as a Director. Welcome ${authenticated.member.username}#${authenticated.member.discriminator}.`,
|
||||
user: {
|
||||
username: authenticated.member.username,
|
||||
discriminator: authenticated.member.discriminator,
|
||||
id: authenticated.member.id,
|
||||
avatarURL: authenticated.member.avatarURL || authenticated.member.defaultAvatarURL,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
this.router.post('/eo', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).send({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof req.body.subject !== 'string' || typeof req.body.body !== 'string' || req.body.subject.length > 256 || req.body.body.length > 1024) {
|
||||
return res.status(400).json({
|
||||
code: this.constants.codes.CLIENT_ERROR,
|
||||
message: this.constants.messages.CLIENT_ERROR,
|
||||
});
|
||||
}
|
||||
|
||||
const id = genUUID();
|
||||
const embed = this.genEmbed(
|
||||
id,
|
||||
'eo',
|
||||
{
|
||||
user: authenticated.director,
|
||||
member: authenticated.member,
|
||||
},
|
||||
{
|
||||
subject: req.body.subject,
|
||||
body: req.body.body,
|
||||
},
|
||||
);
|
||||
const msg = await this.directorLogs.createMessage({ embed });
|
||||
|
||||
const eo = await this.server.client.db.ExecutiveOrder.create({
|
||||
issuer: authenticated.director.userID,
|
||||
subject: req.body.subject,
|
||||
body: req.body.body,
|
||||
at: Date.now(),
|
||||
oID: id,
|
||||
msg: msg.id,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: `Created new Executive Order with ID ${eo.oID} by ${authenticated.member.username}#${authenticated.member.discriminator}`,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.post('/motion', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).send({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof req.body.subject !== 'string' || typeof req.body.body !== 'string' || req.body.subject.length > 256 || req.body.body.length > 1024) {
|
||||
return res.status(400).json({
|
||||
code: this.constants.codes.CLIENT_ERROR,
|
||||
message: this.constants.messages.CLIENT_ERROR,
|
||||
});
|
||||
}
|
||||
|
||||
const id = genUUID();
|
||||
const embed = this.genEmbed(
|
||||
id,
|
||||
'motion',
|
||||
{
|
||||
user: authenticated.director,
|
||||
member: authenticated.member,
|
||||
},
|
||||
{
|
||||
subject: req.body.subject,
|
||||
body: req.body.body,
|
||||
},
|
||||
);
|
||||
const msg = await this.directorLogs.createMessage({ embed });
|
||||
|
||||
const motion = await this.server.client.db.Motion.create({
|
||||
issuer: authenticated.director.userID,
|
||||
subject: req.body.subject,
|
||||
body: req.body.body,
|
||||
at: Date.now(),
|
||||
oID: id,
|
||||
processed: false,
|
||||
msg: msg.id,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: `Created new Motion with ID ${motion.oID} by ${authenticated.member.username}#${authenticated.member.discriminator}`,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.post('/proc', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).send({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof req.body.subject !== 'string' || typeof req.body.body !== 'string' || req.body.subject.length > 256 || req.body.body.length > 1024) {
|
||||
return res.status(400).json({
|
||||
code: this.constants.codes.CLIENT_ERROR,
|
||||
message: this.constants.messages.CLIENT_ERROR,
|
||||
});
|
||||
}
|
||||
|
||||
const id = genUUID();
|
||||
const embed = this.genEmbed(
|
||||
id,
|
||||
'proc',
|
||||
{
|
||||
user: authenticated.director,
|
||||
member: authenticated.member,
|
||||
},
|
||||
{
|
||||
subject: req.body.subject,
|
||||
body: req.body.body,
|
||||
},
|
||||
);
|
||||
const msg = await this.directorLogs.createMessage({ embed });
|
||||
await msg.addReaction('modSuccess:578750988907970567');
|
||||
await msg.addReaction('modError:578750737920688128');
|
||||
await msg.addReaction('🙋');
|
||||
|
||||
const proc = await this.server.client.db.Proclamation.create({
|
||||
issuer: authenticated.director.userID,
|
||||
subject: req.body.subject,
|
||||
body: req.body.body,
|
||||
at: Date.now(),
|
||||
oID: id,
|
||||
processed: false,
|
||||
msg: msg.id,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: `Created new Proclamation with ID ${proc.oID} by ${authenticated.member.username}#${authenticated.member.discriminator}`,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.delete('/eo/:id', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
const eo = await this.server.client.db.ExecutiveOrder.findOne({ oID: req.params.id });
|
||||
|
||||
if (!eo) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
if (eo.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) {
|
||||
return res.status(403).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.codes.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
await eo.deleteOne();
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: 'Executive Order deleted.',
|
||||
});
|
||||
});
|
||||
|
||||
this.router.delete('/motion/:id', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
const motion = await this.server.client.db.Motion.findOne({ oID: req.params.id });
|
||||
|
||||
if (!motion) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
if (motion.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) {
|
||||
return res.status(403).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.codes.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
await motion.deleteOne();
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: 'Motion deleted.',
|
||||
});
|
||||
});
|
||||
|
||||
this.router.delete('/proc/:id', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
const proc = await this.server.client.db.Proclamation.findOne({ oID: req.params.id });
|
||||
|
||||
if (!proc) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
if (proc.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) {
|
||||
return res.status(403).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.codes.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
await proc.deleteOne();
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: 'Proclamation deleted.',
|
||||
});
|
||||
});
|
||||
|
||||
this.router.get('/eo/:id', async (req, res) => {
|
||||
const eo = await this.server.client.db.ExecutiveOrder.findOne({ oID: req.params.id });
|
||||
|
||||
if (!eo) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
const issuer = this.guild.members.get(eo.issuer);
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
issuer: {
|
||||
username: issuer.username,
|
||||
discriminator: issuer.discriminator,
|
||||
avatarURL: issuer.avatarURL,
|
||||
},
|
||||
subject: eo.subject,
|
||||
body: eo.body,
|
||||
issuedAt: eo.at,
|
||||
id: eo.oID,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.get('/motion/:id', async (req, res) => {
|
||||
const motion = await this.server.client.db.Motion.findOne({ oID: req.params.id });
|
||||
|
||||
if (!motion) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
const issuer = this.guild.members.get(motion.issuer);
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
issuer: {
|
||||
username: issuer.username,
|
||||
discriminator: issuer.discriminator,
|
||||
avatarURL: issuer.avatarURL,
|
||||
},
|
||||
subject: motion.subject,
|
||||
body: motion.body,
|
||||
issuedAt: motion.at,
|
||||
id: motion.oID,
|
||||
voteResults: motion.results || null,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.get('/proc/:id', async (req, res) => {
|
||||
const proc = await this.server.client.db.Proclamation.findOne({ oID: req.params.id });
|
||||
|
||||
if (!proc) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
const issuer = this.guild.members.get(proc.issuer);
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
issuer: {
|
||||
username: issuer.username,
|
||||
discriminator: issuer.discriminator,
|
||||
avatarURL: issuer.avatarURL,
|
||||
},
|
||||
subject: proc.subject,
|
||||
body: proc.body,
|
||||
issuedAt: proc.at,
|
||||
id: proc.oID,
|
||||
voteResults: proc.results || null,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.get('/resolution/:id', async (req, res) => {
|
||||
const resolution = await this.server.client.db.Resolution.findOne({ oID: req.params.id });
|
||||
|
||||
if (!resolution) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
const issuer = this.guild.members.get(resolution.issuer);
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
issuer: {
|
||||
username: issuer.username,
|
||||
discriminator: issuer.discriminator,
|
||||
avatarURL: issuer.avatarURL,
|
||||
},
|
||||
subject: resolution.subject,
|
||||
body: resolution.body,
|
||||
issuedAt: resolution.at,
|
||||
id: resolution.oID,
|
||||
voteResults: resolution.results || null,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.patch('/eo/:id', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
const eo = await this.server.client.db.ExecutiveOrder.findOne({ oID: req.params.id });
|
||||
|
||||
if (!eo) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
if (eo.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) {
|
||||
return res.status(403).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.codes.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
await eo.updateOne({
|
||||
subject: req.body.subject || eo.subject,
|
||||
body: req.body.body || eo.body,
|
||||
});
|
||||
|
||||
if (eo.subject !== req.body.subject || eo.body !== req.body.body) {
|
||||
const message = await this.directorLogs.getMessage(eo.msg);
|
||||
const embed = this.genEmbed(
|
||||
eo.oID,
|
||||
'eo',
|
||||
{
|
||||
user: authenticated.director,
|
||||
member: authenticated.member,
|
||||
},
|
||||
{
|
||||
subject: req.body.subject,
|
||||
body: req.body.body,
|
||||
},
|
||||
);
|
||||
|
||||
await message.edit({ embed });
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: 'Updated Executive Order.',
|
||||
});
|
||||
});
|
||||
|
||||
this.router.patch('/motion/:id', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
const motion = await this.server.client.db.Motion.findOne({ oID: req.params.id });
|
||||
|
||||
if (!motion) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
if (motion.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) {
|
||||
return res.status(403).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.codes.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
await motion.updateOne({
|
||||
subject: req.body.subject || motion.subject,
|
||||
body: req.body.body || motion.body,
|
||||
});
|
||||
|
||||
if (motion.subject !== req.body.subject || motion.body !== req.body.body) {
|
||||
const message = await this.directorLogs.getMessage(motion.msg);
|
||||
const embed = this.genEmbed(
|
||||
motion.oID,
|
||||
'motion',
|
||||
{
|
||||
user: authenticated.director,
|
||||
member: authenticated.member,
|
||||
},
|
||||
{
|
||||
subject: req.body.subject,
|
||||
body: req.body.body,
|
||||
},
|
||||
);
|
||||
|
||||
await message.edit({ embed });
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: 'Updated Motion.',
|
||||
});
|
||||
});
|
||||
|
||||
this.router.patch('/proc/:id', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
const proc = await this.server.client.db.Proclamation.findOne({ oID: req.params.id });
|
||||
|
||||
if (!proc) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
if (proc.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) {
|
||||
return res.status(403).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.codes.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
await proc.updateOne({
|
||||
subject: req.body.subject || proc.subject,
|
||||
body: req.body.body || proc.body,
|
||||
});
|
||||
|
||||
if (proc.subject !== req.body.subject || proc.body !== req.body.body) {
|
||||
const message = await this.directorLogs.getMessage(proc.msg);
|
||||
const embed = this.genEmbed(
|
||||
proc.oID,
|
||||
'proc',
|
||||
{
|
||||
user: authenticated.director,
|
||||
member: authenticated.member,
|
||||
},
|
||||
{
|
||||
subject: req.body.subject,
|
||||
body: req.body.body,
|
||||
},
|
||||
);
|
||||
|
||||
await message.edit({ embed });
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: 'Updated Proclamation.',
|
||||
});
|
||||
});
|
||||
|
||||
this.router.patch('/resolution/:id', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
const resolution = await this.server.client.db.Resolution.findOne({ oID: req.params.id });
|
||||
|
||||
if (!resolution) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
if (resolution.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) {
|
||||
return res.status(403).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.codes.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
await resolution.updateOne({
|
||||
subject: req.body.subject || resolution.subject,
|
||||
body: req.body.body || resolution.body,
|
||||
});
|
||||
|
||||
if (resolution.subject !== req.body.subject || resolution.body !== req.body.body) {
|
||||
const message = await this.directorLogs.getMessage(resolution.msg);
|
||||
const embed = this.genEmbed(
|
||||
resolution.oID,
|
||||
'res',
|
||||
{
|
||||
user: authenticated.director,
|
||||
member: authenticated.member,
|
||||
},
|
||||
{
|
||||
subject: req.body.subject,
|
||||
body: req.body.body,
|
||||
},
|
||||
);
|
||||
|
||||
await message.edit({ embed });
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: 'Updated Resolution.',
|
||||
});
|
||||
});
|
||||
|
||||
this.router.get('/eo', async (req, res) => {
|
||||
const page = !Number.isNaN(Number(req.query.page)) ? Number(req.query.page) : 0;
|
||||
const skipped = page || page * 10;
|
||||
|
||||
const eo = await this.server.client.db.ExecutiveOrder.find().skip(skipped);
|
||||
const returned: {
|
||||
oID: string;
|
||||
author: string;
|
||||
issuedAt: number;
|
||||
}[] = [];
|
||||
|
||||
for (const result of eo) {
|
||||
const director = this.guild.members.get(result.issuer);
|
||||
const author = director ? `${director.username}#${director.discriminator}` : 'Deleted User#0000';
|
||||
|
||||
returned.push({
|
||||
oID: result.oID,
|
||||
author,
|
||||
issuedAt: result.at,
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
results: returned,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.get('/motion', async (req, res) => {
|
||||
const page = !Number.isNaN(Number(req.query.page)) ? Number(req.query.page) : 0;
|
||||
const skipped = page || page * 10;
|
||||
|
||||
const motion = await this.server.client.db.Motion.find().skip(skipped);
|
||||
const returned: {
|
||||
oID: string;
|
||||
author: string;
|
||||
issuedAt: number;
|
||||
}[] = [];
|
||||
|
||||
for (const result of motion) {
|
||||
const director = this.guild.members.get(result.issuer);
|
||||
const author = director ? `${director.username}#${director.discriminator}` : 'Deleted User#0000';
|
||||
|
||||
returned.push({
|
||||
oID: result.oID,
|
||||
author,
|
||||
issuedAt: result.at,
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
results: returned,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.get('/proc', async (req, res) => {
|
||||
const page = !Number.isNaN(Number(req.query.page)) ? Number(req.query.page) : 0;
|
||||
const skipped = page || page * 10;
|
||||
|
||||
const proc = await this.server.client.db.Proclamation.find().skip(skipped);
|
||||
const returned: {
|
||||
oID: string;
|
||||
author: string;
|
||||
issuedAt: number;
|
||||
}[] = [];
|
||||
|
||||
for (const result of proc) {
|
||||
const director = this.guild.members.get(result.issuer);
|
||||
const author = director ? `${director.username}#${director.discriminator}` : 'Deleted User#0000';
|
||||
|
||||
returned.push({
|
||||
oID: result.oID,
|
||||
author,
|
||||
issuedAt: result.at,
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
results: returned,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.get('/resolution', async (req, res) => {
|
||||
const page = !Number.isNaN(Number(req.query.page)) ? Number(req.query.page) : 0;
|
||||
const skipped = page || page * 10;
|
||||
|
||||
const resolution = await this.server.client.db.Resolution.find().skip(skipped);
|
||||
const returned: {
|
||||
oID: string;
|
||||
author: string;
|
||||
issuedAt: number;
|
||||
}[] = [];
|
||||
|
||||
for (const result of resolution) {
|
||||
const director = this.guild.members.get(result.issuer);
|
||||
const author = director ? `${director.username}#${director.discriminator}` : 'Deleted User#0000';
|
||||
|
||||
returned.push({
|
||||
oID: result.oID,
|
||||
author,
|
||||
issuedAt: result.at,
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
results: returned,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.put('/motion/confirm/:id', async (req, res) => {
|
||||
const authenticated = await this.authenticate(req);
|
||||
if (!authenticated) {
|
||||
return res.status(401).json({
|
||||
code: this.constants.codes.UNAUTHORIZED,
|
||||
message: this.constants.messages.UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
|
||||
const motion = await this.server.client.db.Motion.findOne({ oID: req.params.id });
|
||||
|
||||
if (!motion) {
|
||||
return res.status(404).json({
|
||||
code: this.constants.codes.NOT_FOUND,
|
||||
message: this.constants.messages.NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
if (Number.isNaN(Number(req.body.yea)) || Number.isNaN(Number(req.body.nay)) || Number.isNaN(Number(req.body.present))) {
|
||||
return res.status(400).json({
|
||||
code: this.constants.codes.CLIENT_ERROR,
|
||||
message: this.constants.messages.CLIENT_ERROR,
|
||||
});
|
||||
}
|
||||
|
||||
const yea = Number(req.body.yea);
|
||||
const nay = Number(req.body.nay);
|
||||
const present = Number(req.body.present);
|
||||
const total = this.guild.members.filter((member) => member.roles.includes(this.directorRole));
|
||||
const absent = total.length - (yea + nay + present);
|
||||
|
||||
await motion.updateOne({
|
||||
processed: true,
|
||||
results: {
|
||||
yea,
|
||||
nay,
|
||||
present,
|
||||
absent,
|
||||
},
|
||||
});
|
||||
|
||||
const confirmationEmbed = this.genEmbed(
|
||||
motion.oID,
|
||||
'confirmMotion',
|
||||
{
|
||||
user: authenticated.director,
|
||||
member: authenticated.member,
|
||||
},
|
||||
{
|
||||
subject: motion.subject,
|
||||
body: motion.body,
|
||||
},
|
||||
);
|
||||
confirmationEmbed.addField('Results', `**Total:** ${total.length}\n**Yea:** ${yea}\n**Nay:** ${nay}\n**Present:** ${present}\n**Absent:** ${absent}`);
|
||||
await this.directorLogs.createMessage({ embed: confirmationEmbed });
|
||||
|
||||
const excludingYea = nay + present + absent;
|
||||
if (yea > excludingYea) {
|
||||
const resolutionEmbed = this.genEmbed(
|
||||
motion.oID,
|
||||
'res',
|
||||
{
|
||||
user: authenticated.director,
|
||||
member: authenticated.member,
|
||||
},
|
||||
{
|
||||
subject: motion.subject,
|
||||
body: motion.body,
|
||||
},
|
||||
);
|
||||
const resolutionMessage = await this.directorLogs.createMessage({ embed: resolutionEmbed });
|
||||
|
||||
await this.server.client.db.Resolution.create({
|
||||
issuer: motion.issuer,
|
||||
subject: motion.subject,
|
||||
body: motion.body,
|
||||
at: Date.now(),
|
||||
oID: motion.oID,
|
||||
results: {
|
||||
yea,
|
||||
nay,
|
||||
present,
|
||||
absent,
|
||||
},
|
||||
msg: resolutionMessage.id,
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: 'Confirmed the results of the Motion.',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import { Server, ServerManagement } from '../../class';
|
||||
|
||||
export default (management: ServerManagement) => new Server(management, 3895, `${__dirname}/routes`);
|
|
@ -0,0 +1 @@
|
|||
export { default as report } from './report';
|
|
@ -0,0 +1,605 @@
|
|||
/* eslint-disable no-bitwise */
|
||||
/* eslint-disable no-continue */
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { TextChannel } from 'eris';
|
||||
import { LocalStorage, Route, Server } from '../../../class';
|
||||
import { ScoreHistoricalRaw } from '../../../models/ScoreHistorical';
|
||||
import { getTotalMessageCount } from '../../../intervals/score';
|
||||
|
||||
export default class Report extends Route {
|
||||
public timeout: Map<string, number>;
|
||||
|
||||
public acceptedOffers: LocalStorage;
|
||||
|
||||
constructor(server: Server) {
|
||||
super(server);
|
||||
this.timeout = new Map();
|
||||
this.acceptedOffers = new LocalStorage('accepted-offers');
|
||||
this.conf = {
|
||||
path: '/report',
|
||||
};
|
||||
}
|
||||
|
||||
protected check(userID: string) {
|
||||
if (this.timeout.has(userID)) {
|
||||
if (this.timeout.get(userID) >= 3) {
|
||||
return true;
|
||||
}
|
||||
this.timeout.set(userID, this.timeout.get(userID) + 1);
|
||||
} else {
|
||||
this.timeout.set(userID, 1);
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (this.timeout.has(userID)) {
|
||||
this.timeout.set(userID, this.timeout.get(userID) - 1);
|
||||
} else {
|
||||
this.timeout.delete(userID);
|
||||
}
|
||||
}, 30000);
|
||||
return false;
|
||||
}
|
||||
|
||||
public bind() {
|
||||
this.router.all('*', (_req, res, next) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', '*');
|
||||
res.setHeader('Access-Control-Allow-Headers', '*');
|
||||
next();
|
||||
});
|
||||
|
||||
this.router.post('/hard', async (req, res) => {
|
||||
try {
|
||||
if (!req.headers.authorization) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
if (!req.body.pin || !req.body.userID || !req.body.reason) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR, message: this.constants.messages.CLIENT_ERROR });
|
||||
if (req.body.reason?.length < 1) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR, message: this.constants.codes.CLIENT_ERROR });
|
||||
|
||||
const merchant = await this.server.client.db.Merchant.findOne({ key: req.headers.authorization }).lean().exec();
|
||||
if (!merchant) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
|
||||
const member = await this.server.client.db.Score.findOne({ userID: req.body.userID, 'pin.2': req.body.pin }).lean().exec();
|
||||
if (!member) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
|
||||
const mem = this.server.client.util.resolveMember(member.userID, this.server.client.guilds.get(this.server.client.config.guildID));
|
||||
if (!mem) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.codes.NOT_FOUND });
|
||||
|
||||
if (member.locked) return res.status(403).json({ code: this.constants.codes.PERMISSION_DENIED, message: this.constants.messages.PERMISSION_DENIED });
|
||||
if (merchant?.type !== 1) return res.status(403).json({ code: this.constants.codes.PERMISSION_DENIED, message: this.constants.messages.PERMISSION_DENIED });
|
||||
|
||||
if (this.check(member.userID)) {
|
||||
await this.server.client.db.Score.updateOne({ userID: member.userID }, { $set: { locked: true } });
|
||||
const chan = await this.server.client.getDMChannel(member.userID);
|
||||
try {
|
||||
await chan.createMessage(`__**Community Report Locked**__\nWe've detected suspicious activity on your Community Report, for the integrity of your report we have automatically locked it. To unlock your report, please run \`${this.server.client.config.prefix}score pref unlock\` in <#468759629334183956>.`);
|
||||
} catch (err) {
|
||||
this.server.client.util.signale.error(`Unable to DM user: ${member.userID} | ${err}`);
|
||||
}
|
||||
return res.status(403).json({ code: this.constants.codes.PERMISSION_DENIED, message: this.constants.messages.PERMISSION_DENIED });
|
||||
}
|
||||
|
||||
const flags = [];
|
||||
if (mem.user.publicFlags) {
|
||||
if ((mem.user.publicFlags & (1 << 0)) === 1 << 0) flags.push('DISCORD_EMPLOYEE');
|
||||
if ((mem.user.publicFlags & (1 << 1)) === 1 << 1) flags.push('PARTNERED_SERVER_OWNER');
|
||||
if ((mem.user.publicFlags & (1 << 2)) === 1 << 2) flags.push('HYPESQUAD_EVENTS');
|
||||
if ((mem.user.publicFlags & (1 << 3)) === 1 << 3) flags.push('BUG_HUNTER_1');
|
||||
if ((mem.user.publicFlags & (1 << 6)) === 1 << 6) flags.push('HOUSE_BRAVERY');
|
||||
if ((mem.user.publicFlags & (1 << 7)) === 1 << 7) flags.push('HOUSE_BRILLIANCE');
|
||||
if ((mem.user.publicFlags & (1 << 8)) === 1 << 8) flags.push('HOUSE_BALANCE');
|
||||
if ((mem.user.publicFlags & (1 << 9)) === 1 << 9) flags.push('EARLY_SUPPORTER');
|
||||
if ((mem.user.publicFlags & (1 << 10)) === 1 << 10) flags.push('TEAM_USER');
|
||||
if ((mem.user.publicFlags & (1 << 12)) === 1 << 12) flags.push('SYSTEM');
|
||||
if ((mem.user.publicFlags & (1 << 14)) === 1 << 14) flags.push('BUG_HUNTER_2');
|
||||
if ((mem.user.publicFlags & (1 << 16)) === 1 << 16) flags.push('VERIFIED_BOT');
|
||||
if ((mem.user.publicFlags & (1 << 17)) === 1 << 17) flags.push('EARLY_VERIFIED_BOT_DEVELOPER');
|
||||
}
|
||||
const set = [];
|
||||
const accounts = await this.server.client.db.Score.find().lean().exec();
|
||||
for (const sc of accounts) {
|
||||
if (sc.total < 200) { continue; }
|
||||
if (sc.total > 800) { set.push(800); continue; }
|
||||
set.push(sc.total);
|
||||
}
|
||||
|
||||
let totalScore: number;
|
||||
let activityScore: number;
|
||||
const moderationScore = Math.round(member.moderation);
|
||||
let roleScore: number;
|
||||
let cloudServicesScore: number;
|
||||
const otherScore = Math.round(member.other);
|
||||
let miscScore: number;
|
||||
|
||||
if (member.total < 200) totalScore = 0;
|
||||
else if (member.total > 800) totalScore = 800;
|
||||
else totalScore = Math.round(member.total);
|
||||
|
||||
if (member.activity < 10) activityScore = 0;
|
||||
else if (member.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activityScore = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12));
|
||||
else activityScore = Math.round(member.activity);
|
||||
|
||||
if (member.roles <= 0) roleScore = 0;
|
||||
else if (member.roles > 54) roleScore = 54;
|
||||
else roleScore = Math.round(member.roles);
|
||||
|
||||
if (member.staff <= 0) miscScore = 0;
|
||||
else miscScore = Math.round(member.staff);
|
||||
|
||||
if (member.cloudServices === 0) cloudServicesScore = 0;
|
||||
else if (member.cloudServices > 10) cloudServicesScore = 10;
|
||||
else cloudServicesScore = Math.round(member.cloudServices);
|
||||
|
||||
const inqs = await this.server.client.db.Inquiry.find({ userID: member.userID }).lean().exec();
|
||||
const inquiries: [{ id?: string, name: string, date: Date }?] = [];
|
||||
if (inqs?.length > 0) {
|
||||
for (const inq of inqs) {
|
||||
const testDate = (new Date(new Date(inq.date).setHours(1460)));
|
||||
if (testDate > new Date()) inquiries.push({ id: inq.iid, name: inq.name, date: inq.date });
|
||||
}
|
||||
}
|
||||
|
||||
const historicalData = await this.server.client.db.ScoreHistorical.find({ userID: member.userID }).lean().exec();
|
||||
const array: ScoreHistoricalRaw[] = [];
|
||||
for (const data of historicalData) {
|
||||
delete data.report?.softInquiries;
|
||||
|
||||
let total: number;
|
||||
let activity: number;
|
||||
const moderation = Math.round(data.report.moderation);
|
||||
let role: number;
|
||||
let cloud: number;
|
||||
const other = Math.round(data.report.other);
|
||||
let misc: number;
|
||||
|
||||
if (data.report.total < 200) total = 0;
|
||||
else if (data.report.total > 800) total = 800;
|
||||
else total = Math.round(data.report.total);
|
||||
|
||||
if (data.report.activity < 10) activity = 0;
|
||||
else if (data.report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activity = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12));
|
||||
else activity = Math.round(data.report.activity);
|
||||
|
||||
if (data.report.roles <= 0) role = 0;
|
||||
else if (data.report.roles > 54) role = 54;
|
||||
else role = Math.round(data.report.roles);
|
||||
|
||||
if (data.report.staff <= 0) role = 0;
|
||||
else misc = Math.round(data.report.staff);
|
||||
|
||||
if (data.report.cloudServices === 0) cloud = 0;
|
||||
else if (data.report.cloudServices > 10) cloud = 10;
|
||||
else cloud = Math.round(data.report.cloudServices);
|
||||
|
||||
data.report.total = total; data.report.activity = activity; data.report.moderation = moderation; data.report.roles = role; data.report.cloudServices = cloud; data.report.other = other; data.report.staff = misc;
|
||||
|
||||
array.push(data);
|
||||
}
|
||||
|
||||
const inq = await this.server.client.report.createInquiry(member.userID, merchant.name, 0, req.body.reason);
|
||||
|
||||
await this.server.client.db.Merchant.updateOne({ key: req.headers.authorization }, { $addToSet: { pulls: { type: 0, reason: req.body.reason, date: new Date() } } });
|
||||
|
||||
return res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: {
|
||||
id: inq.id,
|
||||
userID: member.userID,
|
||||
memberInformation: {
|
||||
username: mem.user.username,
|
||||
discriminator: mem.user.discriminator,
|
||||
joinedServerAt: new Date(mem.joinedAt),
|
||||
createdAt: new Date(mem.createdAt),
|
||||
avatarURL: mem.avatarURL,
|
||||
flags,
|
||||
nitroBoost: mem.premiumSince === null,
|
||||
},
|
||||
percentile: Math.round(this.server.client.util.percentile(set, member.total)),
|
||||
totalScore,
|
||||
activityScore,
|
||||
roleScore,
|
||||
moderationScore,
|
||||
cloudServicesScore,
|
||||
miscScore,
|
||||
otherScore,
|
||||
inquiries,
|
||||
historical: array ?? [],
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
|
||||
this.router.post('/v2/soft', async (req, res) => {
|
||||
try {
|
||||
if (!req.headers.authorization) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
if (!req.body.userID) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR, message: this.constants.messages.CLIENT_ERROR });
|
||||
|
||||
const merchant = await this.server.client.db.Merchant.findOne({ key: req.headers.authorization }).lean().exec();
|
||||
if (!merchant) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
|
||||
const report = await this.server.client.db.Score.findOne({ userID: req.body.userID }).lean().exec();
|
||||
if (!report) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
|
||||
let totalScore: number;
|
||||
|
||||
if (report.total < 200) totalScore = 0;
|
||||
else if (report.total > 800) totalScore = 800;
|
||||
else totalScore = Math.round(report.total);
|
||||
|
||||
await this.server.client.db.Merchant.updateOne({ key: req.headers.authorization }, { $addToSet: { pulls: { type: 1, reason: 'N/A', date: new Date() } } });
|
||||
const mem = this.server.client.util.resolveMember(report.userID, this.server.client.guilds.get(this.server.client.config.guildID));
|
||||
await this.server.client.report.createInquiry(report.userID, merchant.name.toUpperCase(), 1);
|
||||
|
||||
if (!merchant.privileged) {
|
||||
return res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: {
|
||||
userID: report.userID,
|
||||
totalScore,
|
||||
},
|
||||
});
|
||||
}
|
||||
const flags = [];
|
||||
if (mem.user.publicFlags) {
|
||||
if ((mem.user.publicFlags & (1 << 0)) === 1 << 0) flags.push('DISCORD_EMPLOYEE');
|
||||
if ((mem.user.publicFlags & (1 << 1)) === 1 << 1) flags.push('PARTNERED_SERVER_OWNER');
|
||||
if ((mem.user.publicFlags & (1 << 2)) === 1 << 2) flags.push('HYPESQUAD_EVENTS');
|
||||
if ((mem.user.publicFlags & (1 << 3)) === 1 << 3) flags.push('BUG_HUNTER_1');
|
||||
if ((mem.user.publicFlags & (1 << 6)) === 1 << 6) flags.push('HOUSE_BRAVERY');
|
||||
if ((mem.user.publicFlags & (1 << 7)) === 1 << 7) flags.push('HOUSE_BRILLIANCE');
|
||||
if ((mem.user.publicFlags & (1 << 8)) === 1 << 8) flags.push('HOUSE_BALANCE');
|
||||
if ((mem.user.publicFlags & (1 << 9)) === 1 << 9) flags.push('EARLY_SUPPORTER');
|
||||
if ((mem.user.publicFlags & (1 << 10)) === 1 << 10) flags.push('TEAM_USER');
|
||||
if ((mem.user.publicFlags & (1 << 12)) === 1 << 12) flags.push('SYSTEM');
|
||||
if ((mem.user.publicFlags & (1 << 14)) === 1 << 14) flags.push('BUG_HUNTER_2');
|
||||
if ((mem.user.publicFlags & (1 << 16)) === 1 << 16) flags.push('VERIFIED_BOT');
|
||||
if ((mem.user.publicFlags & (1 << 17)) === 1 << 17) flags.push('EARLY_VERIFIED_BOT_DEVELOPER');
|
||||
}
|
||||
const set = [];
|
||||
if (req.query.p?.toString() === 'true') {
|
||||
const accounts = await this.server.client.db.Score.find().lean().exec();
|
||||
for (const sc of accounts) {
|
||||
if (sc.total < 200) { continue; }
|
||||
if (sc.total > 800) { set.push(800); continue; }
|
||||
set.push(sc.total);
|
||||
}
|
||||
}
|
||||
|
||||
let activityScore: number;
|
||||
const moderationScore = Math.round(report.moderation);
|
||||
let roleScore: number;
|
||||
let cloudServicesScore: number;
|
||||
const otherScore = Math.round(report.other);
|
||||
let miscScore: number;
|
||||
|
||||
if (report.activity < 10) activityScore = 0;
|
||||
else if (report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activityScore = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12));
|
||||
else activityScore = Math.round(report.activity);
|
||||
|
||||
if (report.roles <= 0) roleScore = 0;
|
||||
else if (report.roles > 54) roleScore = 54;
|
||||
else roleScore = Math.round(report.roles);
|
||||
|
||||
if (report.staff <= 0) miscScore = 0;
|
||||
else miscScore = Math.round(report.staff);
|
||||
|
||||
if (report.cloudServices === 0) cloudServicesScore = 0;
|
||||
else if (report.cloudServices > 10) cloudServicesScore = 10;
|
||||
else cloudServicesScore = Math.round(report.cloudServices);
|
||||
|
||||
const historicalData = await this.server.client.db.ScoreHistorical.find({ userID: report.userID }).lean().exec();
|
||||
const array: ScoreHistoricalRaw[] = [];
|
||||
for (const data of historicalData) {
|
||||
delete data.report?.softInquiries;
|
||||
delete data.report?.inquiries;
|
||||
|
||||
let total: number;
|
||||
let activity: number;
|
||||
const moderation = Math.round(data.report.moderation);
|
||||
let role: number;
|
||||
let cloud: number;
|
||||
const other = Math.round(data.report.other);
|
||||
let misc: number;
|
||||
|
||||
if (data.report.total < 200) total = 0;
|
||||
else if (data.report.total > 800) total = 800;
|
||||
else total = Math.round(data.report.total);
|
||||
|
||||
if (data.report.activity < 10) activity = 0;
|
||||
else if (data.report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activity = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12));
|
||||
else activity = Math.round(data.report.activity);
|
||||
|
||||
if (data.report.roles <= 0) role = 0;
|
||||
else if (data.report.roles > 54) role = 54;
|
||||
else role = Math.round(data.report.roles);
|
||||
|
||||
if (data.report.staff <= 0) role = 0;
|
||||
else misc = Math.round(data.report.staff);
|
||||
|
||||
if (data.report.cloudServices === 0) cloud = 0;
|
||||
else if (data.report.cloudServices > 10) cloud = 10;
|
||||
else cloud = Math.round(data.report.cloudServices);
|
||||
|
||||
data.report.total = total; data.report.activity = activity; data.report.moderation = moderation; data.report.roles = role; data.report.cloudServices = cloud; data.report.other = other; data.report.staff = misc;
|
||||
|
||||
array.push(data);
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: {
|
||||
userID: report.userID,
|
||||
memberInformation: {
|
||||
username: mem.user.username,
|
||||
discriminator: mem.user.discriminator,
|
||||
joinedServerAt: new Date(mem.joinedAt),
|
||||
createdAt: new Date(mem.createdAt),
|
||||
avatarURL: mem.avatarURL,
|
||||
flags,
|
||||
nitroBoost: mem.premiumSince === null,
|
||||
},
|
||||
totalScore,
|
||||
percentile: Math.round(this.server.client.util.percentile(set, report.total)),
|
||||
activityScore,
|
||||
moderationScore,
|
||||
roleScore,
|
||||
cloudServicesScore,
|
||||
otherScore,
|
||||
miscScore,
|
||||
historical: array ?? [],
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
|
||||
this.router.post('/soft', async (req, res) => {
|
||||
try {
|
||||
if (!req.headers.authorization) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
if (!req.body.pin || !req.body.userID) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR, message: this.constants.messages.CLIENT_ERROR });
|
||||
|
||||
const merchant = await this.server.client.db.Merchant.findOne({ key: req.headers.authorization }).lean().exec();
|
||||
if (!merchant) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
|
||||
const member = await this.server.client.db.Score.findOne({ userID: req.body.userID, 'pin.2': req.body.pin }).lean().exec();
|
||||
if (!member) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
const mem = this.server.client.util.resolveMember(member.userID, this.server.client.guilds.get(this.server.client.config.guildID));
|
||||
if (!mem) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.codes.NOT_FOUND });
|
||||
|
||||
let totalScore: number;
|
||||
|
||||
if (member.total < 200) totalScore = 0;
|
||||
else if (member.total > 800) totalScore = 800;
|
||||
else totalScore = Math.round(member.total);
|
||||
|
||||
await this.server.client.db.Merchant.updateOne({ key: req.headers.authorization }, { $addToSet: { pulls: { type: 1, reason: 'N/A', date: new Date() } } });
|
||||
await this.server.client.report.createInquiry(member.userID, merchant.name.toUpperCase(), 1);
|
||||
|
||||
if (!merchant.privileged) {
|
||||
return res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: {
|
||||
userID: member.userID,
|
||||
totalScore,
|
||||
},
|
||||
});
|
||||
}
|
||||
let activityScore: number;
|
||||
const moderationScore = Math.round(member.moderation);
|
||||
let roleScore: number;
|
||||
let cloudServicesScore: number;
|
||||
const otherScore = Math.round(member.other);
|
||||
let miscScore: number;
|
||||
|
||||
if (member.activity < 10) activityScore = 0;
|
||||
else if (member.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activityScore = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12));
|
||||
else activityScore = Math.round(member.activity);
|
||||
|
||||
if (member.roles <= 0) roleScore = 0;
|
||||
else if (member.roles > 54) roleScore = 54;
|
||||
else roleScore = Math.round(member.roles);
|
||||
|
||||
if (member.staff <= 0) miscScore = 0;
|
||||
else miscScore = Math.round(member.staff);
|
||||
|
||||
if (member.cloudServices === 0) cloudServicesScore = 0;
|
||||
else if (member.cloudServices > 10) cloudServicesScore = 10;
|
||||
else cloudServicesScore = Math.round(member.cloudServices);
|
||||
|
||||
return res.status(200).json({
|
||||
code: this.constants.codes.SUCCESS,
|
||||
message: {
|
||||
userID: member.userID,
|
||||
totalScore,
|
||||
activityScore,
|
||||
moderationScore,
|
||||
roleScore,
|
||||
cloudServicesScore,
|
||||
otherScore,
|
||||
miscScore,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
|
||||
this.router.get('/web', async (req, res) => {
|
||||
try {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
if (this.timeout.has(req.ip)) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
if (!req.query.pin) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
const args = req.query.pin.toString();
|
||||
this.timeout.set(req.ip, 1);
|
||||
setTimeout(() => this.timeout.delete(req.ip), 1800000);
|
||||
let score = await this.server.client.db.Score.findOne({ pin: [Number(args.split('-')[0]), Number(args.split('-')[1]), Number(args.split('-')[2])] }).lean().exec();
|
||||
if (!score) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
const member = await this.server.client.getRESTGuildMember(this.constants.discord.SERVER_ID, score.userID);
|
||||
if (!member) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
let updated = false;
|
||||
if (req.query.staff) {
|
||||
// eslint-disable-next-line no-shadow
|
||||
const args = req.query.staff.toString();
|
||||
const staffScore = await this.server.client.db.Score.findOne({ pin: [Number(args.split('-')[0]), Number(args.split('-')[1]), Number(args.split('-')[2])] }).lean().exec();
|
||||
if (!staffScore) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
if (!staffScore.staff) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
this.timeout.delete(req.ip);
|
||||
if (staffScore.userID === score.userID) {
|
||||
updated = true;
|
||||
await this.server.client.report.createInquiry(member.user.id, `${member.username} via report.libraryofcode.org @ IP ${req.ip}`, 1);
|
||||
} else {
|
||||
await this.server.client.report.createInquiry(member.user.id, 'Library of Code sp-us | Staff Team via report.libraryofcode.org', 1);
|
||||
}
|
||||
} else if (!updated) {
|
||||
await this.server.client.report.createInquiry(member.user.id, `${member.username} via report.libraryofcode.org @ IP ${req.ip}`, 1);
|
||||
}
|
||||
score = await this.server.client.db.Score.findOne({ pin: [Number(args.split('-')[0]), Number(args.split('-')[1]), Number(args.split('-')[2])] }).lean().exec();
|
||||
|
||||
let totalScore = '0';
|
||||
let activityScore = '0';
|
||||
let moderationScore = '0';
|
||||
let roleScore = '0';
|
||||
let cloudServicesScore = '0';
|
||||
let otherScore = '0';
|
||||
let miscScore = '0';
|
||||
|
||||
if (score.total < 200) totalScore = '---';
|
||||
else if (score.total > 800) totalScore = '800';
|
||||
else totalScore = `${score.total}`;
|
||||
|
||||
if (score.activity < 10) activityScore = '---';
|
||||
else if (score.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activityScore = String(Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12)));
|
||||
else activityScore = `${score.activity}`;
|
||||
|
||||
if (score.roles <= 0) roleScore = '---';
|
||||
else if (score.roles > 54) roleScore = '54';
|
||||
else roleScore = `${score.roles}`;
|
||||
|
||||
moderationScore = `${score.moderation}`;
|
||||
|
||||
if (score.other === 0) otherScore = '---';
|
||||
else otherScore = `${score.other}`;
|
||||
|
||||
if (score.staff <= 0) miscScore = '---';
|
||||
else miscScore = `${score.staff}`;
|
||||
|
||||
if (score.cloudServices === 0) cloudServicesScore = '---';
|
||||
else if (score.cloudServices > 10) cloudServicesScore = '10';
|
||||
else cloudServicesScore = `${score.cloudServices}`;
|
||||
|
||||
const moderations = await this.server.client.db.Moderation.find({ userID: score.userID }).lean().exec();
|
||||
|
||||
const historical = await this.server.client.db.ScoreHistorical.find({ userID: score.userID }).lean().exec();
|
||||
|
||||
for (const data of historical) {
|
||||
let total: number;
|
||||
let activity: number;
|
||||
const moderation = Math.round(data.report.moderation);
|
||||
let role: number;
|
||||
let cloud: number;
|
||||
const other = Math.round(data.report.other);
|
||||
let misc: number;
|
||||
|
||||
if (data.report.total < 200) total = 0;
|
||||
else if (data.report.total > 800) total = 800;
|
||||
else total = Math.round(data.report.total);
|
||||
|
||||
if (data.report.activity < 10) activity = 0;
|
||||
else if (data.report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activity = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12));
|
||||
else activity = Math.round(data.report.activity);
|
||||
|
||||
if (data.report.roles <= 0) role = 0;
|
||||
else if (data.report.roles > 54) role = 54;
|
||||
else role = Math.round(data.report.roles);
|
||||
|
||||
if (data.report.staff <= 0) role = 0;
|
||||
else misc = Math.round(data.report.staff);
|
||||
|
||||
if (data.report.cloudServices === 0) cloud = 0;
|
||||
else if (data.report.cloudServices > 10) cloud = 10;
|
||||
else cloud = Math.round(data.report.cloudServices);
|
||||
|
||||
data.report.total = total; data.report.activity = activity; data.report.moderation = moderation; data.report.roles = role; data.report.cloudServices = cloud; data.report.other = other; data.report.staff = misc;
|
||||
}
|
||||
|
||||
const inqs = await this.server.client.db.Inquiry.find({ userID: score.userID }).lean().exec();
|
||||
const hardInquiries: [{ id?: string, name: string, reason: string, date: Date }?] = [];
|
||||
const softInquiries: [{ id?: string, name: string, date: Date }?] = [];
|
||||
for (const inq of inqs) {
|
||||
if (inq.type === 0) {
|
||||
hardInquiries.push({ id: inq.iid, name: inq.name, reason: inq.reason, date: inq.date });
|
||||
} else if (inq.type === 1) {
|
||||
softInquiries.push({ name: inq.name, date: inq.date });
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
name: `${member.username}#${member.discriminator}`,
|
||||
avatarURL: member.avatarURL,
|
||||
userID: score.userID,
|
||||
pin: score.pin?.join('-'),
|
||||
score: totalScore,
|
||||
activityScore,
|
||||
cloudServicesScore,
|
||||
moderationScore,
|
||||
roleScore,
|
||||
otherScore,
|
||||
miscScore,
|
||||
notify: score.notify,
|
||||
locked: !!score.locked,
|
||||
totalModerations: moderations?.length > 0 ? moderations.length : 0,
|
||||
inquiries: hardInquiries?.length > 0 ? hardInquiries.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) : [],
|
||||
softInquiries: softInquiries?.length > 0 ? softInquiries.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) : [],
|
||||
historical: historical ?? [],
|
||||
lastUpdated: score.lastUpdate,
|
||||
});
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
|
||||
this.router.get('/offer', async (req, res) => {
|
||||
try {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
if (!req.query.code) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
if (await this.acceptedOffers.get(req.query.code.toString())) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
let offer: {
|
||||
userID?: string,
|
||||
staffID?: string,
|
||||
channelID?: string,
|
||||
messageID?: string,
|
||||
pin?: string,
|
||||
name?: string,
|
||||
department?: string,
|
||||
date?: Date,
|
||||
};
|
||||
|
||||
try {
|
||||
offer = <{
|
||||
userID?: string,
|
||||
staffID?: string,
|
||||
channelID?: string,
|
||||
messageID?: string,
|
||||
pin?: string,
|
||||
name?: string,
|
||||
department?: string,
|
||||
date?: Date,
|
||||
}>jwt.verify(req.query.code.toString(), this.server.client.config.internalKey);
|
||||
} catch {
|
||||
return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
}
|
||||
const chan = <TextChannel>this.server.client.guilds.get(this.constants.discord.SERVER_ID).channels.get(offer.channelID);
|
||||
await chan.createMessage(`__**PRE-APPROVED OFFER ACCEPTED**__\n<@${offer.staffID}>`);
|
||||
const message = await chan.getMessage(offer.messageID);
|
||||
const args = [];
|
||||
args.push(offer.userID, 'hard');
|
||||
`${offer.department}:${offer.name}`.split(' ').forEach((item) => args.push(item));
|
||||
await this.server.client.commands.get('score').run(message, args);
|
||||
await this.acceptedOffers.set(req.query.code.toString(), true);
|
||||
return res.sendStatus(200);
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import { Server, ServerManagement } from '../../class';
|
||||
|
||||
export default (management: ServerManagement) => new Server(management, 3891, `${__dirname}/routes`);
|
|
@ -0,0 +1 @@
|
|||
export { default as root } from './root';
|
|
@ -0,0 +1,32 @@
|
|||
import { LocalStorage, Route, Server } from '../../../class';
|
||||
|
||||
export default class Root extends Route {
|
||||
constructor(server: Server) {
|
||||
super(server);
|
||||
this.conf = {
|
||||
path: '/',
|
||||
};
|
||||
}
|
||||
|
||||
public bind() {
|
||||
this.router.get('/m/:id', async (req, res) => {
|
||||
try {
|
||||
const id = req.params.id.split('.')[0];
|
||||
const file = await this.server.client.db.File.findOne({ identifier: id });
|
||||
if (!file) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.messages.NOT_FOUND });
|
||||
if (file.downloaded >= file.maxDownloads) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.messages.NOT_FOUND });
|
||||
if (req.query.d === '1') {
|
||||
res.contentType('text/html');
|
||||
const decomp = await LocalStorage.decompress(file.data);
|
||||
res.status(200).send(decomp);
|
||||
} else {
|
||||
res.contentType(file.mimeType);
|
||||
res.status(200).send(file.data);
|
||||
}
|
||||
return await file.updateOne({ $inc: { downloaded: 1 } });
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import locsh from './loc.sh/main';
|
||||
import crins from './cr.ins/main';
|
||||
import commlibraryofcodeorg from './comm.libraryofcode.org/main';
|
||||
import boardins from './board.ins/main';
|
||||
|
||||
export default {
|
||||
'board.ins': boardins,
|
||||
'loc.sh': locsh,
|
||||
'cr.ins': crins,
|
||||
'comm.libraryofcode.org': commlibraryofcodeorg,
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
import { Server, ServerManagement } from '../../class';
|
||||
|
||||
export default (management: ServerManagement) => {
|
||||
const server = new Server(management, 3890, `${__dirname}/routes`, false);
|
||||
return server;
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export { default as root } from './root';
|
||||
export { default as internal } from './internal';
|
|
@ -0,0 +1,119 @@
|
|||
import axios from 'axios';
|
||||
import bodyParser from 'body-parser';
|
||||
import { Route, Server, LocalStorage } from '../../../class';
|
||||
// import acknowledgements from '../../../configs/acknowledgements.json';
|
||||
|
||||
export default class Internal extends Route {
|
||||
public timeout: Set<string>;
|
||||
|
||||
public acceptedOffers: LocalStorage;
|
||||
|
||||
constructor(server: Server) {
|
||||
super(server);
|
||||
this.timeout = new Set();
|
||||
this.conf = {
|
||||
path: '/int',
|
||||
};
|
||||
this.acceptedOffers = new LocalStorage('accepted-offers');
|
||||
}
|
||||
|
||||
public bind() {
|
||||
this.router.get('/directory', async (req, res) => {
|
||||
try {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
if (req.query.id) {
|
||||
let member = this.server.client.guilds.get(this.server.client.config.guildID).members.get(req.query.id.toString());
|
||||
if (!member) member = await this.server.client.getRESTGuildMember(this.server.client.config.guildID, req.query.id.toString());
|
||||
if (!member) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.messages.NOT_FOUND });
|
||||
const pagerNumber = await this.server.client.db.PagerNumber.findOne({ individualAssignID: member.id }).lean().exec();
|
||||
let status = false;
|
||||
if (member.roles.includes('446104438969466890') || member.roles.includes('701481967149121627')) status = true;
|
||||
return res.status(200).json({ staff: status, username: member.user.username, discriminator: member.user.discriminator, nick: member.nick, avatarURL: member.user.avatarURL, pager: pagerNumber?.num });
|
||||
}
|
||||
const acknowledgements = await this.server.client.db.Staff.find().lean().exec();
|
||||
return res.status(200).json(acknowledgements);
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
|
||||
this.router.get('/offer', async (_req, res) => {
|
||||
try {
|
||||
return res.status(410).json({ code: this.constants.codes.DEPRECATED, message: this.constants.codes.DEPRECATED });
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
|
||||
this.router.get('/score', async (_req, res) => {
|
||||
try {
|
||||
return res.status(410).json({ code: this.constants.codes.DEPRECATED, message: this.constants.codes.DEPRECATED });
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
|
||||
this.router.get('/id', async (req, res) => {
|
||||
try {
|
||||
if (req.query?.auth?.toString() !== this.server.client.config.internalKey) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
if (!req.query.pin) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR });
|
||||
const args = req.query.pin.toString();
|
||||
const user = await this.server.client.db.Score.findOne({ pin: [Number(args.split('-')[0]), Number(args.split('-')[1]), Number(args.split('-')[2])] }).lean().exec();
|
||||
if (!user) return res.status(404).json({ code: this.constants.codes.ACCOUNT_NOT_FOUND, message: this.constants.messages.NOT_FOUND });
|
||||
return res.status(200).json({ userID: user.userID });
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
|
||||
this.router.get('/pin', async (req, res) => {
|
||||
try {
|
||||
if (req.query?.auth?.toString() !== this.server.client.config.internalKey) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
if (!req.query.id) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR });
|
||||
|
||||
const report = await this.server.client.db.Score.findOne({ userID: req.query.id.toString() });
|
||||
if (!report) return res.status(404).json({ code: this.constants.codes.ACCOUNT_NOT_FOUND, message: this.constants.messages.NOT_FOUND });
|
||||
return res.status(200).json({ pin: report.pin });
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
|
||||
this.router.post('/sub', bodyParser.raw({ type: 'application/json' }), async (req, res) => {
|
||||
try {
|
||||
const event = this.server.client.stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], this.server.client.config.stripeSubSigningSecret);
|
||||
const data = <any> event.data.object;
|
||||
|
||||
switch (event.type) {
|
||||
default:
|
||||
return res.sendStatus(400);
|
||||
case 'customer.subscription.created':
|
||||
if (data.items.data[0].price.product === 'prod_Hi4EYmf2am5VZt') {
|
||||
const customer = await this.server.client.db.Customer.findOne({ cusID: data.customer }).lean().exec();
|
||||
if (!customer) return res.sendStatus(404);
|
||||
await axios({
|
||||
method: 'get',
|
||||
url: `https://api.cloud.libraryofcode.org/wh/t3?userID=${customer.userID}&auth=${this.server.client.config.internalKey}`,
|
||||
});
|
||||
res.sendStatus(200);
|
||||
}
|
||||
break;
|
||||
case 'customer.subscription.deleted':
|
||||
if (data.items.data[0].price.product === 'prod_Hi4EYmf2am5VZt') {
|
||||
const customer = await this.server.client.db.Customer.findOne({ cusID: data.customer }).lean().exec();
|
||||
if (!customer) return res.sendStatus(404);
|
||||
await axios({
|
||||
method: 'get',
|
||||
url: `https://api.cloud.libraryofcode.org/wh/t3-rm?userID=${customer.userID}&auth=${this.server.client.config.internalKey}`,
|
||||
});
|
||||
return res.sendStatus(200);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import { Route, Server } from '../../../class';
|
||||
import { RedirectRaw } from '../../../models';
|
||||
|
||||
export default class Root extends Route {
|
||||
constructor(server: Server) {
|
||||
super(server);
|
||||
this.conf = {
|
||||
path: '/',
|
||||
};
|
||||
}
|
||||
|
||||
public bind() {
|
||||
this.router.get('/', (_req, res) => res.redirect('https://www.libraryofcode.org/'));
|
||||
|
||||
this.router.get('/dash', async (req, res) => {
|
||||
try {
|
||||
const lookup = await this.server.client.db.CustomerPortal.findOne({ key: req.query.q?.toString() });
|
||||
if (!lookup) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.messages.NOT_FOUND });
|
||||
if (new Date(lookup.expiresOn) < new Date()) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
|
||||
|
||||
const customer = await this.server.client.db.Customer.findOne({ userID: lookup.userID });
|
||||
if (!customer) {
|
||||
const newCus = await this.server.client.stripe.customers.create({
|
||||
email: lookup.emailAddress,
|
||||
metadata: {
|
||||
userID: lookup.userID,
|
||||
username: lookup.username,
|
||||
},
|
||||
});
|
||||
await (new this.server.client.db.Customer({
|
||||
cusID: newCus.id,
|
||||
userID: lookup.userID,
|
||||
})).save();
|
||||
const billingURL = await this.server.client.stripe.billingPortal.sessions.create({
|
||||
customer: newCus.id,
|
||||
return_url: 'https://www.libraryofcode.org',
|
||||
});
|
||||
res.redirect(302, billingURL.url);
|
||||
return await lookup.updateOne({ $set: { used: true } });
|
||||
}
|
||||
const billingURL = await this.server.client.stripe.billingPortal.sessions.create({
|
||||
customer: customer.cusID,
|
||||
return_url: 'https://www.libraryofcode.org',
|
||||
});
|
||||
res.redirect(302, billingURL.url);
|
||||
return await lookup.updateOne({ $set: { used: true } });
|
||||
} catch (err) {
|
||||
return this.handleError(err, res);
|
||||
}
|
||||
});
|
||||
|
||||
this.router.get('/:key', async (req, res) => {
|
||||
try {
|
||||
const link: RedirectRaw = await this.server.client.db.Redirect.findOne({ key: req.params.key }).lean().exec();
|
||||
if (!link) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.messages.NOT_FOUND });
|
||||
res.redirect(link.to);
|
||||
return await this.server.client.db.Redirect.updateOne({ key: req.params.key }, { $inc: { visitedCount: 1 } });
|
||||
} catch (err) {
|
||||
this.server.client.util.handleError(err);
|
||||
return res.status(500).json({ code: this.constants.codes.SERVER_ERROR, message: this.constants.messages.SERVER_ERROR });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
import Stripe from 'stripe';
|
||||
import eris from 'eris';
|
||||
import pluris from 'pluris';
|
||||
import mongoose from 'mongoose';
|
||||
import { promises as fs } from 'fs';
|
||||
import { Collection, Command, LocalStorage, Queue, Util, ServerManagement, Event } from '.';
|
||||
import {
|
||||
Customer, CustomerInterface,
|
||||
CustomerPortal, CustomerPortalInterface,
|
||||
ExecutiveOrder, ExecutiveOrderInterface,
|
||||
File, FileInterface,
|
||||
Inquiry, InquiryInterface,
|
||||
Member, MemberInterface,
|
||||
Merchant, MerchantInterface,
|
||||
Moderation, ModerationInterface,
|
||||
Motion, MotionInterface,
|
||||
NNTrainingData, NNTrainingDataInterface,
|
||||
Note, NoteInterface,
|
||||
PagerNumber, PagerNumberInterface,
|
||||
Proclamation, ProclamationInterface,
|
||||
Promo, PromoInterface,
|
||||
Rank, RankInterface,
|
||||
Redirect, RedirectInterface,
|
||||
Resolution, ResolutionInterface,
|
||||
Score, ScoreInterface,
|
||||
ScoreHistorical, ScoreHistoricalInterface,
|
||||
Staff, StaffInterface,
|
||||
Stat, StatInterface,
|
||||
} from '../models';
|
||||
import { Config } from '../../types'; // eslint-disable-line
|
||||
|
||||
pluris(eris);
|
||||
|
||||
export default class Client extends eris.Client {
|
||||
public config: Config;
|
||||
|
||||
public commands: Collection<Command>;
|
||||
|
||||
public events: Collection<Event>;
|
||||
|
||||
public intervals: Collection<NodeJS.Timeout>;
|
||||
|
||||
public util: Util;
|
||||
|
||||
public serverManagement: ServerManagement;
|
||||
|
||||
public queue: Queue;
|
||||
|
||||
public stripe: Stripe;
|
||||
|
||||
public db: { Customer: mongoose.Model<CustomerInterface>, CustomerPortal: mongoose.Model<CustomerPortalInterface>, ExecutiveOrder: mongoose.Model<ExecutiveOrderInterface>, File: mongoose.Model<FileInterface>, Inquiry: mongoose.Model<InquiryInterface>, Member: mongoose.Model<MemberInterface>, Merchant: mongoose.Model<MerchantInterface>, Moderation: mongoose.Model<ModerationInterface>, Motion: mongoose.Model<MotionInterface>, NNTrainingData: mongoose.Model<NNTrainingDataInterface>, Note: mongoose.Model<NoteInterface>, PagerNumber: mongoose.Model<PagerNumberInterface>, Proclamation: mongoose.Model<ProclamationInterface>, Promo: mongoose.Model<PromoInterface>, Rank: mongoose.Model<RankInterface>, Redirect: mongoose.Model<RedirectInterface>, Resolution: mongoose.Model<ResolutionInterface>, Score: mongoose.Model<ScoreInterface>, ScoreHistorical: mongoose.Model<ScoreHistoricalInterface>, Staff: mongoose.Model<StaffInterface>, Stat: mongoose.Model<StatInterface>, local: { muted: LocalStorage } };
|
||||
|
||||
constructor(token: string, options?: eris.ClientOptions) {
|
||||
super(token, options);
|
||||
this.commands = new Collection<Command>();
|
||||
this.events = new Collection<Event>();
|
||||
this.intervals = new Collection<NodeJS.Timeout>();
|
||||
this.queue = new Queue(this);
|
||||
this.db = { Customer, CustomerPortal, ExecutiveOrder, File, Inquiry, Member, Merchant, Moderation, Motion, NNTrainingData, Note, PagerNumber, Promo, Proclamation, Rank, Redirect, Resolution, Score, ScoreHistorical, Staff, Stat, local: { muted: new LocalStorage('muted') } };
|
||||
}
|
||||
|
||||
get report() {
|
||||
return this.util.report;
|
||||
}
|
||||
|
||||
get pbx() {
|
||||
return this.util.pbx;
|
||||
}
|
||||
|
||||
|
||||
public async loadDatabase() {
|
||||
await mongoose.connect(this.config.mongoDB, { useNewUrlParser: true, useUnifiedTopology: true, poolSize: 50 });
|
||||
|
||||
const statMessages = await this.db.Stat.findOne({ name: 'messages' });
|
||||
const statCommands = await this.db.Stat.findOne({ name: 'commands' });
|
||||
const statPages = await this.db.Stat.findOne({ name: 'pages' });
|
||||
const statRequests = await this.db.Stat.findOne({ name: 'requests' });
|
||||
|
||||
if (!statMessages) {
|
||||
await (new this.db.Stat({ name: 'messages', value: 0 }).save());
|
||||
}
|
||||
if (!statCommands) {
|
||||
await (new this.db.Stat({ name: 'commands', value: 0 }).save());
|
||||
}
|
||||
if (!statPages) {
|
||||
await (new this.db.Stat({ name: 'pages', value: 0 }).save());
|
||||
}
|
||||
if (!statRequests) {
|
||||
await (new this.db.Stat({ name: 'requests', value: 0 }).save());
|
||||
}
|
||||
}
|
||||
|
||||
public loadPlugins() {
|
||||
this.util = new Util(this);
|
||||
this.serverManagement = new ServerManagement(this);
|
||||
this.stripe = new Stripe(this.config.stripeKey, { apiVersion: null, typescript: true });
|
||||
}
|
||||
|
||||
public async loadIntervals() {
|
||||
const intervalFiles = await fs.readdir(`${__dirname}/../intervals`);
|
||||
intervalFiles.forEach((file) => {
|
||||
const intervalName = file.split('.')[0];
|
||||
if (file === 'index.js') return;
|
||||
const interval: NodeJS.Timeout = (require(`${__dirname}/../intervals/${file}`).default)(this);
|
||||
this.intervals.add(intervalName, interval);
|
||||
this.util.signale.success(`Successfully loaded interval: ${intervalName}`);
|
||||
});
|
||||
}
|
||||
|
||||
public async loadEvents(eventFiles: { [s: string]: typeof Event; } | ArrayLike<typeof Event>) {
|
||||
const evtFiles = Object.entries<typeof Event>(eventFiles);
|
||||
for (const [name, Ev] of evtFiles) {
|
||||
const event = new Ev(this);
|
||||
this.events.add(event.event, event);
|
||||
this.on(event.event, event.run);
|
||||
this.util.signale.success(`Successfully loaded event: ${name}`);
|
||||
delete require.cache[require.resolve(`${__dirname}/../events/${name}`)];
|
||||
}
|
||||
}
|
||||
|
||||
public async loadCommands(commandFiles: { [s: string]: typeof Command; } | ArrayLike<typeof Command>) {
|
||||
const cmdFiles = Object.values<typeof Command>(commandFiles);
|
||||
for (const Cmd of cmdFiles) {
|
||||
const command = new Cmd(this);
|
||||
if (command.subcmds.length) {
|
||||
command.subcmds.forEach((C) => {
|
||||
const cmd: Command = new C(this);
|
||||
command.subcommands.add(cmd.name, cmd);
|
||||
this.util.signale.success(`Successfully loaded subcommand ${cmd.name} under ${command.name}`);
|
||||
});
|
||||
}
|
||||
delete command.subcmds;
|
||||
this.commands.add(command.name, command);
|
||||
this.util.signale.success(`Successfully loaded command: ${command.name}`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/**
|
||||
* Hold a bunch of something
|
||||
*/
|
||||
export default class Collection<V> extends Map<string, V> {
|
||||
baseObject: new (...args: any[]) => V;
|
||||
|
||||
/**
|
||||
* Creates an instance of Collection
|
||||
*/
|
||||
constructor(iterable: Iterable<[string, V]>|object = null) {
|
||||
if (iterable && iterable instanceof Array) {
|
||||
super(iterable);
|
||||
} else if (iterable && iterable instanceof Object) {
|
||||
super(Object.entries(iterable));
|
||||
} else {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map to array
|
||||
* ```js
|
||||
* [value, value, value]
|
||||
* ```
|
||||
*/
|
||||
toArray(): V[] {
|
||||
return [...this.values()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Map to object
|
||||
* ```js
|
||||
* { key: value, key: value, key: value }
|
||||
* ```
|
||||
*/
|
||||
toObject(): { [key: string]: V } {
|
||||
const obj: { [key: string]: V } = {};
|
||||
for (const [key, value] of this.entries()) {
|
||||
obj[key] = value;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an object
|
||||
*
|
||||
* If baseObject, add only if instance of baseObject
|
||||
*
|
||||
* If no baseObject, add
|
||||
* @param key The key of the object
|
||||
* @param value The object data
|
||||
* @param replace Whether to replace an existing object with the same key
|
||||
* @return The existing or newly created object
|
||||
*/
|
||||
add(key: string, value: V, replace: boolean = false): V {
|
||||
if (this.has(key) && !replace) {
|
||||
return this.get(key);
|
||||
}
|
||||
if (this.baseObject && !(value instanceof this.baseObject)) return null;
|
||||
|
||||
this.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first object to make the function evaluate true
|
||||
* @param func A function that takes an object and returns something
|
||||
* @return The first matching object, or `null` if no match
|
||||
*/
|
||||
find(func: Function): V {
|
||||
for (const item of this.values()) {
|
||||
if (func(item)) return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array with the results of applying the given function to each element
|
||||
* @param callbackfn A function that takes an object and returns something
|
||||
*/
|
||||
map<U>(callbackfn: (value?: V, index?: number, array?: V[]) => U): U[] {
|
||||
const arr = [];
|
||||
for (const item of this.values()) {
|
||||
arr.push(callbackfn(item));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the objects that make the function evaluate true
|
||||
* @param func A function that takes an object and returns true if it matches
|
||||
*/
|
||||
filter(func: (value: V) => boolean): V[] {
|
||||
const arr = [];
|
||||
for (const item of this.values()) {
|
||||
if (func(item)) {
|
||||
arr.push(item);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if at least one element passes the test implemented by the provided function. Returns true if yes, or false if not.
|
||||
* @param func A function that takes an object and returns true if it matches
|
||||
*/
|
||||
some(func: (value: V) => boolean) {
|
||||
for (const item of this.values()) {
|
||||
if (func(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an object
|
||||
* @param key The key of the object
|
||||
* @param value The updated object data
|
||||
*/
|
||||
update(key: string, value: V) {
|
||||
return this.add(key, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an object
|
||||
* @param key The key of the object
|
||||
* @returns The removed object, or `null` if nothing was removed
|
||||
*/
|
||||
remove(key: string): V {
|
||||
const item = this.get(key);
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
this.delete(key);
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a random object from the Collection
|
||||
* @returns The random object or `null` if empty
|
||||
*/
|
||||
random(): V {
|
||||
if (!this.size) {
|
||||
return null;
|
||||
}
|
||||
return Array.from(this.values())[Math.floor(Math.random() * this.size)];
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `[Collection<${this.baseObject.name}>]`;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
import { Member, Message, TextableChannel } from 'eris';
|
||||
import { Client, Collection } from '.';
|
||||
|
||||
export default class Command {
|
||||
public client: Client;
|
||||
|
||||
/**
|
||||
* The name of the command
|
||||
*/
|
||||
public name: string;
|
||||
/**
|
||||
* The description for the command.
|
||||
*/
|
||||
|
||||
public description: string;
|
||||
|
||||
/**
|
||||
* Usage for the command.
|
||||
*/
|
||||
public usage: string;
|
||||
/**
|
||||
* The aliases for the command.
|
||||
*/
|
||||
|
||||
public aliases: string[];
|
||||
|
||||
/**
|
||||
* - **0:** Everyone
|
||||
* - **1:** Associates+
|
||||
* - **2:** Core Team+
|
||||
* - **3:** Moderators, Supervisor, & Board of Directors
|
||||
* - **4:** Technicians, Supervisor, & Board of Directors
|
||||
* - **5:** Moderators, Technicians, Supervisor, & Board of Directors
|
||||
* - **6:** Supervisor+
|
||||
* - **7:** Board of Directors
|
||||
*/
|
||||
public permissions: number;
|
||||
|
||||
/**
|
||||
* Determines if the command is only available in server.
|
||||
*/
|
||||
public guildOnly: boolean;
|
||||
|
||||
/**
|
||||
* Determines if the command is enabled or not.
|
||||
*/
|
||||
|
||||
public subcommands?: Collection<Command>;
|
||||
|
||||
public subcmds?: any[];
|
||||
|
||||
public enabled: boolean;
|
||||
|
||||
public run(message: Message, args: string[]): Promise<any> { return Promise.resolve(); }
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
|
||||
this.aliases = [];
|
||||
|
||||
this.subcommands = new Collection<Command>();
|
||||
|
||||
this.subcmds = [];
|
||||
}
|
||||
|
||||
get mainGuild() {
|
||||
return this.client.guilds.get(this.client.config.guildID);
|
||||
}
|
||||
|
||||
public checkPermissions(member: Member): boolean {
|
||||
if (member.id === '278620217221971968' || member.id === '253600545972027394') return true;
|
||||
switch (this.permissions) {
|
||||
case 0:
|
||||
return true;
|
||||
case 1:
|
||||
return member.roles.some((r) => ['701481967149121627', '453689940140883988', '455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
|
||||
case 2:
|
||||
return member.roles.some((r) => ['453689940140883988', '455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
|
||||
case 3:
|
||||
return member.roles.some((r) => ['455972169449734144', '701454855952138300', '662163685439045632'].includes(r));
|
||||
case 4:
|
||||
return member.roles.some((r) => ['701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
|
||||
case 5:
|
||||
return member.roles.some((r) => ['455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
|
||||
case 6:
|
||||
return member.roles.some((r) => ['701454855952138300', '662163685439045632'].includes(r));
|
||||
case 7:
|
||||
return member.roles.includes('662163685439045632');
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public checkCustomPermissions(member: Member, permission: number): boolean {
|
||||
if (member.id === '278620217221971968' || member.id === '253600545972027394') return true;
|
||||
switch (permission) {
|
||||
case 0:
|
||||
return true;
|
||||
case 1:
|
||||
return member.roles.some((r) => ['701481967149121627', '453689940140883988', '455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
|
||||
case 2:
|
||||
return member.roles.some((r) => ['453689940140883988', '455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
|
||||
case 3:
|
||||
return member.roles.some((r) => ['455972169449734144', '701454855952138300', '662163685439045632'].includes(r));
|
||||
case 4:
|
||||
return member.roles.some((r) => ['701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
|
||||
case 5:
|
||||
return member.roles.some((r) => ['455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
|
||||
case 6:
|
||||
return member.roles.some((r) => ['701454855952138300', '662163685439045632'].includes(r));
|
||||
case 7:
|
||||
return member.roles.includes('662163685439045632');
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public error(channel: TextableChannel, text: string): Promise<Message> {
|
||||
return channel.createMessage(`***${this.client.util.emojis.ERROR} ${text}***`);
|
||||
}
|
||||
|
||||
public success(channel: TextableChannel, text: string): Promise<Message> {
|
||||
return channel.createMessage(`***${this.client.util.emojis.SUCCESS} ${text}***`);
|
||||
}
|
||||
|
||||
public loading(channel: TextableChannel, text: string): Promise<Message> {
|
||||
return channel.createMessage(`***${this.client.util.emojis.LOADING} ${text}***`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { Client } from '.';
|
||||
|
||||
export default class Event {
|
||||
public client: Client
|
||||
|
||||
public event: string;
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
this.event = '';
|
||||
this.run = this.run.bind(this);
|
||||
}
|
||||
|
||||
public async run(...args: any[]): Promise<void> { return Promise.resolve(); }
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import ARI from 'ari-client';
|
||||
import { PBX } from '.';
|
||||
|
||||
export default class Handler {
|
||||
public pbx: PBX;
|
||||
|
||||
public app: string;
|
||||
|
||||
public options: {
|
||||
available?: boolean,
|
||||
}
|
||||
|
||||
constructor(pbx: PBX) {
|
||||
this.pbx = pbx;
|
||||
this.options = {};
|
||||
}
|
||||
|
||||
get client() { return this.pbx.client; }
|
||||
|
||||
public run(event: ARI.Event, channel: ARI.Channel): Promise<any> { return Promise.resolve(); }
|
||||
|
||||
public async unavailable(event: ARI.Event, channel: ARI.Channel) {
|
||||
const playback = await channel.play({
|
||||
media: 'sound:all-outgoing-lines-unavailable',
|
||||
}, undefined);
|
||||
playback.once('PlaybackFinished', () => channel.hangup());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
/* eslint-disable no-constant-condition */
|
||||
import { promises as fs, accessSync, constants, writeFileSync } from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { join } from 'path';
|
||||
import { gzip, gzipSync, unzip } from 'zlib';
|
||||
|
||||
type JSONData = [{ key: string, value: any }?];
|
||||
|
||||
|
||||
/**
|
||||
* Persistant local JSON-based storage.
|
||||
* - auto-locking system to prevent corrupted data
|
||||
* - uses gzip compression to keep DB storage space utilization low
|
||||
* @author Matthew <matthew@staff.libraryofcode.org>
|
||||
*/
|
||||
export default class LocalStorage {
|
||||
protected storagePath: string;
|
||||
|
||||
private locked: boolean = false;
|
||||
|
||||
constructor(dbName: string, dir = `${__dirname}/../../localstorage`) {
|
||||
this.storagePath = join(__dirname, '../../localstorage') || dir;
|
||||
this.init();
|
||||
}
|
||||
|
||||
private init() {
|
||||
try {
|
||||
accessSync(this.storagePath, constants.F_OK);
|
||||
} catch {
|
||||
const setup = [];
|
||||
const data = gzipSync(JSON.stringify(setup));
|
||||
writeFileSync(this.storagePath, data);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses data using gzip.
|
||||
* @param data The data to be compressed.
|
||||
* ```ts
|
||||
* await LocalStorage.compress('hello!');
|
||||
* ```
|
||||
*/
|
||||
static async compress(data: string): Promise<Buffer> {
|
||||
const func = promisify(gzip);
|
||||
const comp = <Buffer>await func(data);
|
||||
return comp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses data using gzip.
|
||||
* @param data The data to be decompressed.
|
||||
* ```ts
|
||||
* const compressed = await LocalStorage.compress('data');
|
||||
* const decompressed = await LocalStorage.decompress(compressed);
|
||||
* console.log(decompressed); // logs 'data';
|
||||
* ```
|
||||
*/
|
||||
static async decompress(data: Buffer): Promise<string> {
|
||||
const func = promisify(unzip);
|
||||
const uncomp = <Buffer>await func(data);
|
||||
return uncomp.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves one data from the store.
|
||||
* If the store has multiple entries for the same key, this function will only return the first entry.
|
||||
* ```ts
|
||||
* await LocalStorage.get<type>('data-key');
|
||||
* ```
|
||||
* @param key The key for the data entry.
|
||||
*/
|
||||
public async get<T>(key: string): Promise<T> {
|
||||
while (true) {
|
||||
if (!this.locked) break;
|
||||
}
|
||||
this.locked = true;
|
||||
|
||||
const file = await fs.readFile(this.storagePath);
|
||||
const uncomp = await LocalStorage.decompress(file);
|
||||
this.locked = false;
|
||||
const json: JSONData = JSON.parse(uncomp);
|
||||
const result = json.filter((data) => data.key === key);
|
||||
if (!result[0]) return null;
|
||||
return result[0].value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves multiple data keys/values from the store.
|
||||
* This function will return all of the values matching the key you provided exactly. Use `LocalStorage.get();` if possible.
|
||||
* ```ts
|
||||
* await LocalStorage.get<type>('data-key');
|
||||
* @param key The key for the data entry.
|
||||
*/
|
||||
public async getMany<T>(key: string): Promise<{ key: string, value: T }[]> {
|
||||
while (true) {
|
||||
if (!this.locked) break;
|
||||
}
|
||||
this.locked = true;
|
||||
|
||||
const file = await fs.readFile(this.storagePath);
|
||||
const uncomp = await LocalStorage.decompress(file);
|
||||
this.locked = false;
|
||||
const json: JSONData = JSON.parse(uncomp);
|
||||
const result = json.filter((data) => data.key === key);
|
||||
if (result.length < 1) return null;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a key/value pair and creates a new data entry.
|
||||
* @param key The key for the data entry.
|
||||
* @param value The value for the data entry, can be anything that is valid JSON.
|
||||
* @param options.override [DEPRECATED] By default, this function will error if the key you're trying to set already exists. Set this option to true to override that setting.
|
||||
* ```ts
|
||||
* await LocalStorage.set('data-key', 'test');
|
||||
* ```
|
||||
*/
|
||||
public async set(key: string, value: any): Promise<void> {
|
||||
while (true) {
|
||||
if (!this.locked) break;
|
||||
}
|
||||
this.locked = true;
|
||||
|
||||
const file = await fs.readFile(this.storagePath);
|
||||
const uncomp = await LocalStorage.decompress(file);
|
||||
const json: JSONData = JSON.parse(uncomp);
|
||||
json.push({ key, value });
|
||||
const comp = await LocalStorage.compress(JSON.stringify(json));
|
||||
await fs.writeFile(this.storagePath, comp);
|
||||
this.locked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the data for the specified key.
|
||||
* **Warning:** This function will delete ALL matching entries.
|
||||
* ```ts
|
||||
* await LocalStorage.del('data-key');
|
||||
* ```
|
||||
* @param key The key for the data entry.
|
||||
*/
|
||||
public async del(key: string): Promise<void> {
|
||||
while (true) {
|
||||
if (!this.locked) break;
|
||||
}
|
||||
this.locked = true;
|
||||
|
||||
const file = await fs.readFile(this.storagePath);
|
||||
const uncomp = await LocalStorage.decompress(file);
|
||||
const json: JSONData = JSON.parse(uncomp);
|
||||
const filtered = json.filter((data) => data.key !== key);
|
||||
const comp = await LocalStorage.compress(JSON.stringify(filtered));
|
||||
await fs.writeFile(this.storagePath, comp);
|
||||
this.locked = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
/* eslint-disable no-bitwise */
|
||||
import { Member, User } from 'eris';
|
||||
import { randomBytes } from 'crypto';
|
||||
import moment, { unitOfTime } from 'moment';
|
||||
import { Client, RichEmbed } from '.';
|
||||
import { Moderation as ModerationModel, ModerationInterface } from '../models';
|
||||
import { moderation as channels } from '../configs/channels.json';
|
||||
|
||||
export default class Moderation {
|
||||
public client: Client;
|
||||
|
||||
public logChannels: {
|
||||
modlogs: string
|
||||
};
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
this.logChannels = {
|
||||
modlogs: channels.modlogs,
|
||||
};
|
||||
}
|
||||
|
||||
public checkPermissions(member: Member, moderator: Member): boolean {
|
||||
if (member.id === moderator.id) return false;
|
||||
if (member.roles.some((r) => ['662163685439045632', '455972169449734144', '453689940140883988'].includes(r))) return false;
|
||||
const bit = member.permission.allow;
|
||||
if ((bit | 8) === bit) return false;
|
||||
if ((bit | 20) === bit) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts some sort of time based duration to milliseconds based on length.
|
||||
* @param time The time, examples: 2h, 1m, 1w
|
||||
*/
|
||||
public convertTimeDurationToMilliseconds(time: string): number {
|
||||
const lockLength = time.match(/[a-z]+|[^a-z]+/gi);
|
||||
const length = Number(lockLength[0]);
|
||||
const unit = lockLength[1] as unitOfTime.Base;
|
||||
return moment.duration(length, unit).asMilliseconds();
|
||||
}
|
||||
|
||||
public async ban(user: User, moderator: Member, duration: number, reason?: string): Promise<ModerationInterface> {
|
||||
if (reason && reason.length > 512) throw new Error('Ban reason cannot be longer than 512 characters');
|
||||
await this.client.guilds.get(this.client.config.guildID).banMember(user.id, 7, reason);
|
||||
const logID = randomBytes(2).toString('hex');
|
||||
const mod = new ModerationModel({
|
||||
userID: user.id,
|
||||
logID,
|
||||
moderatorID: moderator.id,
|
||||
reason: reason || null,
|
||||
type: 5,
|
||||
date: new Date(),
|
||||
});
|
||||
const now: number = Date.now();
|
||||
let date: Date;
|
||||
let processed = true;
|
||||
if (duration > 0) {
|
||||
date = new Date(now + duration);
|
||||
processed = false;
|
||||
} else date = null;
|
||||
const expiration = { date, processed };
|
||||
mod.expiration = expiration;
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle(`Case ${logID} | Ban`);
|
||||
embed.setColor('#e74c3c');
|
||||
embed.setAuthor(user.username, user.avatarURL);
|
||||
embed.setThumbnail(user.avatarURL);
|
||||
embed.addField('User', `<@${user.id}>`, true);
|
||||
embed.addField('Moderator', `<@${moderator.id}>`, true);
|
||||
if (reason) {
|
||||
embed.addField('Reason', reason, true);
|
||||
}
|
||||
if (date) {
|
||||
embed.addField('Expiration', moment(date).calendar(), true);
|
||||
}
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
this.client.createMessage(this.logChannels.modlogs, { embed });
|
||||
return mod.save();
|
||||
}
|
||||
|
||||
public async unban(userID: string, moderator: Member, reason?: string): Promise<ModerationInterface> {
|
||||
this.client.unbanGuildMember(this.client.config.guildID, userID, reason);
|
||||
const user = await this.client.getRESTUser(userID);
|
||||
if (!user) throw new Error('Cannot get user.');
|
||||
const logID = randomBytes(2).toString('hex');
|
||||
const mod = new ModerationModel({
|
||||
userID,
|
||||
logID,
|
||||
moderatorID: moderator.id,
|
||||
reason: reason || null,
|
||||
type: 4,
|
||||
date: new Date(),
|
||||
});
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle(`Case ${logID} | Unban`);
|
||||
embed.setColor('#1abc9c');
|
||||
embed.setAuthor(user.username, user.avatarURL);
|
||||
embed.setThumbnail(user.avatarURL);
|
||||
embed.addField('User', `<@${user.id}>`, true);
|
||||
embed.addField('Moderator', `<@${moderator.id}>`, true);
|
||||
if (reason) {
|
||||
embed.addField('Reason', reason, true);
|
||||
}
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
this.client.createMessage(this.logChannels.modlogs, { embed });
|
||||
return mod.save();
|
||||
}
|
||||
|
||||
public async mute(user: User, moderator: Member, duration: number, reason?: string): Promise<ModerationInterface> {
|
||||
if (reason && reason.length > 512) throw new Error('Mute reason cannot be longer than 512 characters');
|
||||
const member = await this.client.getRESTGuildMember(this.client.config.guildID, user.id);
|
||||
if (!member) throw new Error('Cannot find member.');
|
||||
await member.addRole('478373942638149643', `Muted by ${moderator.username}#${moderator.discriminator}`);
|
||||
const logID = randomBytes(2).toString('hex');
|
||||
const mod = new ModerationModel({
|
||||
userID: user.id,
|
||||
logID,
|
||||
moderatorID: moderator.id,
|
||||
reason: reason || null,
|
||||
type: 2,
|
||||
date: new Date(),
|
||||
});
|
||||
const now: number = Date.now();
|
||||
let date: Date;
|
||||
let processed = true;
|
||||
if (duration > 0) {
|
||||
date = new Date(now + duration);
|
||||
processed = false;
|
||||
} else date = null;
|
||||
const expiration = { date, processed };
|
||||
mod.expiration = expiration;
|
||||
await this.client.db.local.muted.set(`muted-${member.id}`, true);
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle(`Case ${logID} | Mute`);
|
||||
embed.setColor('#ffff00');
|
||||
embed.setAuthor(user.username, user.avatarURL);
|
||||
embed.setThumbnail(user.avatarURL);
|
||||
embed.addField('User', `<@${user.id}>`, true);
|
||||
embed.addField('Moderator', `<@${moderator.id}>`, true);
|
||||
if (reason) {
|
||||
embed.addField('Reason', reason, true);
|
||||
}
|
||||
if (date) {
|
||||
embed.addField('Expiration', moment(date).calendar(), true);
|
||||
}
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
this.client.createMessage(this.logChannels.modlogs, { embed });
|
||||
return mod.save();
|
||||
}
|
||||
|
||||
public async unmute(userID: string, moderator: Member, reason?: string): Promise<ModerationInterface> {
|
||||
const member = await this.client.getRESTGuildMember(this.client.config.guildID, userID);
|
||||
const user = await this.client.getRESTUser(userID);
|
||||
if (member) {
|
||||
await member.removeRole('478373942638149643');
|
||||
}
|
||||
const logID = randomBytes(2).toString('hex');
|
||||
const mod = new ModerationModel({
|
||||
userID,
|
||||
logID,
|
||||
moderatorID: moderator.id,
|
||||
reason: reason || null,
|
||||
type: 1,
|
||||
date: new Date(),
|
||||
});
|
||||
|
||||
await this.client.db.local.muted.del(`muted-${member.id}`);
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle(`Case ${logID} | Unmute`);
|
||||
embed.setColor('#1abc9c');
|
||||
embed.setAuthor(user.username, user.avatarURL);
|
||||
embed.setThumbnail(user.avatarURL);
|
||||
embed.addField('User', `<@${user.id}>`, true);
|
||||
embed.addField('Moderator', `<@${moderator.id}>`, true);
|
||||
if (reason) {
|
||||
embed.addField('Reason', reason, true);
|
||||
}
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
this.client.createMessage(this.logChannels.modlogs, { embed });
|
||||
return mod.save();
|
||||
}
|
||||
|
||||
public async kick(user: Member | User, moderator: Member, reason?: string): Promise<ModerationInterface> {
|
||||
if (reason && reason.length > 512) throw new Error('Kick reason cannot be longer than 512 characters');
|
||||
await this.client.guilds.get(this.client.config.guildID).kickMember(user.id, reason);
|
||||
const logID = randomBytes(2).toString('hex');
|
||||
const mod = new ModerationModel({
|
||||
userID: user.id,
|
||||
logID,
|
||||
moderatorID: moderator.id,
|
||||
reason: reason || null,
|
||||
type: 5,
|
||||
date: new Date(),
|
||||
});
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle(`Case ${logID} | Kick`);
|
||||
embed.setColor('#e74c3c');
|
||||
embed.setAuthor(user.username, user.avatarURL);
|
||||
embed.setThumbnail(user.avatarURL);
|
||||
embed.addField('User', `<@${user.id}>`, true);
|
||||
embed.addField('Moderator', `<@${moderator.id}>`, true);
|
||||
if (reason) {
|
||||
embed.addField('Reason', reason, true);
|
||||
}
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
this.client.createMessage(this.logChannels.modlogs, { embed });
|
||||
return mod.save();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/* eslint-disable no-continue */
|
||||
import ARIClient from 'ari-client';
|
||||
import AMIClient from 'asterisk-manager';
|
||||
import GoogleTTS, { TextToSpeechClient } from '@google-cloud/text-to-speech';
|
||||
import { Client, Collection, Handler } from '.';
|
||||
|
||||
export default class PBX {
|
||||
public client: Client;
|
||||
|
||||
public handlers: Collection<Handler>;
|
||||
|
||||
public ari: ARIClient.Client;
|
||||
|
||||
public ami: any;
|
||||
|
||||
public tts: TextToSpeechClient;
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
|
||||
this.handlers = new Collection<Handler>();
|
||||
|
||||
this.load();
|
||||
}
|
||||
|
||||
private async load() {
|
||||
this.ari = await ARIClient.connect('http://10.8.0.1:8088/ari', 'cr0', this.client.config.ariClientKey);
|
||||
this.ari.start(['cr-zero', 'page-dtmf']);
|
||||
|
||||
this.ami = new AMIClient(5038, '10.8.0.1', 'cr', this.client.config.amiClientKey);
|
||||
|
||||
process.env.GOOGLE_APPLICATION_CREDENTIALS = `${__dirname}/../../google.json`;
|
||||
this.tts = new GoogleTTS.TextToSpeechClient();
|
||||
this.start();
|
||||
}
|
||||
|
||||
public start() {
|
||||
const handlers = Object.values<typeof Handler>(require(`${__dirname}/../pbx`));
|
||||
for (const HandlerFile of handlers) {
|
||||
const handler = new HandlerFile(this);
|
||||
if (!handler.app) continue;
|
||||
if (!handler.options?.available) {
|
||||
this.ari.on('StasisStart', async (event, channel) => {
|
||||
if (event.application !== handler.app) return;
|
||||
await handler.unavailable(event, channel);
|
||||
});
|
||||
} else {
|
||||
this.ari.on('StasisStart', async (event, channel) => {
|
||||
if (event.application !== handler.app) return;
|
||||
try {
|
||||
await handler.run(event, channel);
|
||||
} catch (err) {
|
||||
this.client.util.handleError(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.handlers.add(handler.app, handler);
|
||||
this.client.util.signale.success(`Successfully loaded PBX Handler ${handler.app}`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-eval */
|
||||
import Bull from 'bull';
|
||||
import cron from 'cron';
|
||||
import { TextableChannel, TextChannel } from 'eris';
|
||||
import { Client, RichEmbed } from '.';
|
||||
import { ScoreInterface, InqType as InquiryType } from '../models';
|
||||
|
||||
import { apply as Apply } from '../commands';
|
||||
|
||||
export default class Queue {
|
||||
public client: Client;
|
||||
|
||||
public queues: { score: Bull.Queue };
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
this.queues = {
|
||||
score: new Bull('score', { prefix: 'queue::score' }),
|
||||
};
|
||||
this.setProcessors();
|
||||
this.setCronJobs();
|
||||
}
|
||||
|
||||
protected setCronJobs() {
|
||||
const historialCommunityReportJob = new cron.CronJob('0 20 * * *', async () => {
|
||||
try {
|
||||
const reports = await this.client.db.Score.find().lean().exec();
|
||||
const startDate = new Date();
|
||||
|
||||
for (const report of reports) {
|
||||
const inqs = await this.client.db.Inquiry.find({ userID: report.userID });
|
||||
const data = new this.client.db.ScoreHistorical({
|
||||
userID: report.userID,
|
||||
report,
|
||||
inquiries: inqs.map((inq) => inq._id),
|
||||
date: startDate,
|
||||
});
|
||||
await data.save();
|
||||
}
|
||||
} catch (err) {
|
||||
this.client.util.handleError(err);
|
||||
}
|
||||
});
|
||||
|
||||
historialCommunityReportJob.start();
|
||||
}
|
||||
|
||||
public async jobCounts() {
|
||||
const data = {
|
||||
waiting: 0,
|
||||
active: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
delayed: 0,
|
||||
};
|
||||
for (const entry of Object.entries(this.queues)) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const counts = await entry[1].getJobCounts();
|
||||
data.waiting += counts.waiting;
|
||||
data.active += counts.active;
|
||||
data.completed += counts.completed;
|
||||
data.failed += counts.failed;
|
||||
data.delayed += counts.delayed;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
protected listeners() {
|
||||
this.queues.score.on('active', (job) => {
|
||||
this.client.util.signale.pending(`${job.id} has become active.`);
|
||||
});
|
||||
this.queues.score.on('completed', (job) => {
|
||||
this.client.util.signale.success(`Job with id ${job.id} has been completed`);
|
||||
});
|
||||
this.queues.score.on('error', async (err) => {
|
||||
this.client.util.handleError(err);
|
||||
});
|
||||
}
|
||||
|
||||
protected setProcessors() {
|
||||
this.queues.score.process('score::inquiry', async (job: Bull.Job<{ inqID: string, userID: string, name: string, type: InquiryType, reason?: string }>) => {
|
||||
const member = this.client.util.resolveMember(job.data.userID, this.client.guilds.get(this.client.config.guildID));
|
||||
const report = await this.client.db.Score.findOne({ userID: job.data.userID }).lean().exec();
|
||||
const embed = new RichEmbed();
|
||||
if (job.data.type === InquiryType.HARD) {
|
||||
embed.setTitle('Inquiry Notification');
|
||||
embed.setDescription(job.data.inqID);
|
||||
embed.setColor('#800080');
|
||||
embed.addField('Member', `${member.user.username}#${member.user.discriminator} | <@${job.data.userID}>`, true);
|
||||
embed.addField('Type', 'HARD', true);
|
||||
embed.addField('Department/Service', job.data.name.toUpperCase(), true);
|
||||
embed.addField('Reason', job.data.reason ?? 'N/A', true);
|
||||
embed.setTimestamp();
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
if (report.notify === true) {
|
||||
await this.client.getDMChannel(job.data.userID).then((chan) => {
|
||||
chan.createMessage(`__**Community Score - Hard Pull Notification**__\n*You have signed up to be notified whenever your hard score has been pulled. See \`?score\` for more information.*\n\n**Department/Service:** ${job.data.name.toUpperCase()}`);
|
||||
}).catch(() => {});
|
||||
}
|
||||
} else {
|
||||
embed.setTitle('Inquiry Notification');
|
||||
embed.setColor('#00FFFF');
|
||||
embed.addField('Member', `${member.user.username}#${member.user.discriminator} | <@${job.data.userID}>`, true);
|
||||
embed.addField('Type', 'SOFT', true);
|
||||
embed.addField('Department/Service', job.data.name.toUpperCase(), true);
|
||||
embed.setTimestamp();
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
}
|
||||
const log = <TextChannel> this.client.guilds.get(this.client.config.guildID).channels.get('611584771356622849');
|
||||
log.createMessage({ embed }).catch(() => {});
|
||||
});
|
||||
this.queues.score.process('score::update', async (job: Bull.Job<{ score: ScoreInterface, total: number, activity: number, roles: number, moderation: number, cloudServices: number, other: number, staff: number }>) => {
|
||||
await this.client.db.Score.updateOne({ userID: job.data.score.userID }, { $set: { total: job.data.total, activity: job.data.activity, roles: job.data.roles, moderation: job.data.moderation, cloudServices: job.data.cloudServices, other: job.data.other, staff: job.data.staff, lastUpdate: new Date() } });
|
||||
if (!job.data.score.pin || job.data.score.pin?.length < 1) {
|
||||
await this.client.db.Score.updateOne({ userID: job.data.score.userID }, { $set: { pin: [this.client.util.randomNumber(100, 999), this.client.util.randomNumber(10, 99), this.client.util.randomNumber(1000, 9999)] } });
|
||||
}
|
||||
});
|
||||
this.queues.score.process('score::apply', async (job: Bull.Job<{ channelInformation: { messageID: string, guildID: string, channelID: string }, url: string, userID: string, func?: string }>) => {
|
||||
const application = await Apply.apply(this.client, job.data.url, job.data.userID);
|
||||
const guild = this.client.guilds.get(job.data.channelInformation.guildID);
|
||||
const channel = <TextableChannel> guild.channels.get(job.data.channelInformation.channelID);
|
||||
const message = await channel.getMessage(job.data.channelInformation.messageID);
|
||||
const member = guild.members.get(job.data.userID);
|
||||
await message.delete();
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Application Decision');
|
||||
if (member) {
|
||||
embed.setAuthor(member.username, member.avatarURL);
|
||||
}
|
||||
if (application.decision === 'APPROVED') {
|
||||
embed.setColor('#50c878');
|
||||
} else if (application.decision === 'DECLINED') {
|
||||
embed.setColor('#fe0000');
|
||||
} else if (application.decision === 'PRE-DECLINE') {
|
||||
embed.setColor('#ffa500');
|
||||
} else {
|
||||
embed.setColor('#eeeeee');
|
||||
}
|
||||
const chan = await this.client.getDMChannel(job.data.userID);
|
||||
if (chan && application.token) {
|
||||
chan.createMessage(`__**Application Decision Document**__\n*This document provides detailed information about the decision given to you by EDS.*\n\nhttps://eds.libraryofcode.org/dec/${application.token}`);
|
||||
}
|
||||
embed.setDescription(`This application was processed by __${application.processedBy}__ on behalf of the vendor, department, or service who operates this application. Please contact the vendor for further information about your application if needed.`);
|
||||
embed.addField('Status', application.decision, true);
|
||||
embed.addField('User ID', job.data.userID, true);
|
||||
embed.addField('Application ID', application.id, true);
|
||||
embed.addField('Job ID', job.id.toString(), true);
|
||||
embed.setFooter(`${this.client.user.username} via Electronic Decision Service [EDS]`, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
await channel.createMessage({ content: `<@${job.data.userID}>`, embed });
|
||||
if (job.data.func) {
|
||||
const func = eval(job.data.func);
|
||||
if (application.status === 'SUCCESS' && application.decision === 'APPROVED') await func(this.client, job.data.userID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public addInquiry(inqID: string, userID: string, name: string, type: InquiryType, reason?: string) {
|
||||
return this.queues.score.add('score::inquiry', { inqID, userID, name, type, reason });
|
||||
}
|
||||
|
||||
public updateScore(score: ScoreInterface, total: number, activity: number, roles: number, moderation: number, cloudServices: number, other: number, staff: number) {
|
||||
return this.queues.score.add('score::update', { score, total, activity, roles, moderation, cloudServices, other, staff });
|
||||
}
|
||||
|
||||
public processApplication(channelInformation: { messageID: string, guildID: string, channelID: string }, url: string, userID: string, func?: string) {
|
||||
return this.queues.score.add('score::apply', { channelInformation, url, userID, func });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
import { v4 as uuid } from 'uuid';
|
||||
import { Client } from '.';
|
||||
import { InqType } from '../models';
|
||||
|
||||
export default class Report {
|
||||
public client: Client;
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public async createInquiry(userID: string, name: string, type: InqType, reason?: string) {
|
||||
const report = await this.client.db.Score.findOne({ userID }).lean().exec();
|
||||
const member = this.client.util.resolveMember(userID, this.client.guilds.get(this.client.config.guildID));
|
||||
if (!report || !member) return null;
|
||||
if (type === InqType.HARD && report.locked) return null;
|
||||
const mod = await (new this.client.db.Inquiry({
|
||||
iid: uuid(),
|
||||
userID,
|
||||
name,
|
||||
type,
|
||||
reason,
|
||||
date: new Date(),
|
||||
report,
|
||||
}).save());
|
||||
|
||||
this.client.queue.addInquiry(mod.iid, userID, name, type, reason);
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
/* public async soft(userID: string) {
|
||||
const report = await this.client.db.Score.findOne({ userID });
|
||||
if (!report) return null;
|
||||
let totalScore: number;
|
||||
let activityScore: number;
|
||||
const moderationScore = Math.round(report.moderation);
|
||||
let roleScore: number;
|
||||
let cloudServicesScore: number;
|
||||
const otherScore = Math.round(report.other);
|
||||
let miscScore: number;
|
||||
|
||||
if (report.total < 200) totalScore = 0;
|
||||
else if (report.total > 800) totalScore = 800;
|
||||
else totalScore = Math.round(report.total);
|
||||
|
||||
if (report.activity < 10) activityScore = 0;
|
||||
else if (report.activity > Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12))) activityScore = Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12));
|
||||
else activityScore = Math.round(report.activity);
|
||||
|
||||
if (report.roles <= 0) roleScore = 0;
|
||||
else if (report.roles > 54) roleScore = 54;
|
||||
else roleScore = Math.round(report.roles);
|
||||
|
||||
if (report.staff <= 0) miscScore = 0;
|
||||
else miscScore = Math.round(report.staff);
|
||||
|
||||
if (report.cloudServices === 0) cloudServicesScore = 0;
|
||||
else if (report.cloudServices > 10) cloudServicesScore = 10;
|
||||
else cloudServicesScore = Math.round(report.cloudServices);
|
||||
|
||||
const historicalData = await this.client.db.ScoreHistorical.find({ userID: member.userID }).lean().exec();
|
||||
const array: ScoreHistoricalRaw[] = [];
|
||||
for (const data of historicalData) {
|
||||
let total: number;
|
||||
let activity: number;
|
||||
const moderation = Math.round(data.report.moderation);
|
||||
let role: number;
|
||||
let cloud: number;
|
||||
const other = Math.round(data.report.other);
|
||||
let misc: number;
|
||||
|
||||
if (data.report.total < 200) total = 0;
|
||||
else if (data.report.total > 800) total = 800;
|
||||
else total = Math.round(data.report.total);
|
||||
|
||||
if (data.report.activity < 10) activity = 0;
|
||||
else if (data.report.activity > Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12))) activity = Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12));
|
||||
else activity = Math.round(data.report.activity);
|
||||
|
||||
if (data.report.roles <= 0) role = 0;
|
||||
else if (data.report.roles > 54) role = 54;
|
||||
else role = Math.round(data.report.roles);
|
||||
|
||||
if (data.report.staff <= 0) role = 0;
|
||||
else misc = Math.round(data.report.staff);
|
||||
|
||||
if (data.report.cloudServices === 0) cloud = 0;
|
||||
else if (data.report.cloudServices > 10) cloud = 10;
|
||||
else cloud = Math.round(data.report.cloudServices);
|
||||
|
||||
data.report.total = total; data.report.activity = activity; data.report.moderation = moderation; data.report.roles = role; data.report.cloudServices = cloud; data.report.other = other; data.report.staff = misc;
|
||||
|
||||
array.push(data);
|
||||
}
|
||||
} */
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
|
||||
import { EmbedOptions } from 'eris';
|
||||
|
||||
export default class RichEmbed implements EmbedOptions {
|
||||
title?: string
|
||||
|
||||
type?: string
|
||||
|
||||
description?: string
|
||||
|
||||
url?: string
|
||||
|
||||
timestamp?: string | Date
|
||||
|
||||
color?: number
|
||||
|
||||
footer?: { text: string, icon_url?: string, proxy_icon_url?: string}
|
||||
|
||||
image?: { url?: string, proxy_url?: string, height?: number, width?: number }
|
||||
|
||||
thumbnail?: { url?: string, proxy_url?: string, height?: number, width?: number }
|
||||
|
||||
video?: { url: string, height?: number, width?: number }
|
||||
|
||||
provider?: { name: string, url?: string}
|
||||
|
||||
author?: { name: string, url?: string, proxy_icon_url?: string, icon_url?: string}
|
||||
|
||||
fields?: {name: string, value: string, inline?: boolean}[]
|
||||
|
||||
constructor(data: EmbedOptions = {}) {
|
||||
this.title = data.title;
|
||||
this.description = data.description;
|
||||
this.url = data.url;
|
||||
this.color = data.color;
|
||||
this.author = data.author;
|
||||
this.timestamp = data.timestamp;
|
||||
this.fields = data.fields || [];
|
||||
this.thumbnail = data.thumbnail;
|
||||
this.image = data.image;
|
||||
this.footer = data.footer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of this embed.
|
||||
*/
|
||||
public setTitle(title: string) {
|
||||
if (typeof title !== 'string') throw new TypeError('RichEmbed titles must be a string.');
|
||||
if (title.length > 256) throw new RangeError('RichEmbed titles may not exceed 256 characters.');
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this embed.
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
if (typeof description !== 'string') throw new TypeError('RichEmbed descriptions must be a string.');
|
||||
if (description.length > 2048) throw new RangeError('RichEmbed descriptions may not exceed 2048 characters.');
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of this embed.
|
||||
*/
|
||||
public setURL(url: string) {
|
||||
if (typeof url !== 'string') throw new TypeError('RichEmbed URLs must be a string.');
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) url = `https://${url}`;
|
||||
this.url = encodeURI(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of this embed.
|
||||
*/
|
||||
public setColor(color: string | number) {
|
||||
if (typeof color === 'string' || typeof color === 'number') {
|
||||
if (typeof color === 'string') {
|
||||
const regex = /[^a-f0-9]/gi;
|
||||
color = color.replace(/#/g, '');
|
||||
if (regex.test(color)) throw new RangeError('Hexadecimal colours must not contain characters other than 0-9 and a-f.');
|
||||
color = parseInt(color, 16);
|
||||
} else if (color < 0 || color > 16777215) throw new RangeError('Base 10 colours must not be less than 0 or greater than 16777215.');
|
||||
this.color = color;
|
||||
return this;
|
||||
}
|
||||
throw new TypeError('RichEmbed colours must be hexadecimal as string or number.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the author of this embed.
|
||||
*/
|
||||
public setAuthor(name: string, icon_url?: string, url?: string) {
|
||||
if (typeof name !== 'string') throw new TypeError('RichEmbed Author names must be a string.');
|
||||
if (url && typeof url !== 'string') throw new TypeError('RichEmbed Author URLs must be a string.');
|
||||
if (icon_url && typeof icon_url !== 'string') throw new TypeError('RichEmbed Author icons must be a string.');
|
||||
this.author = { name, icon_url, url };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timestamp of this embed.
|
||||
*/
|
||||
public setTimestamp(timestamp = new Date()) {
|
||||
if (Number.isNaN(timestamp.getTime())) throw new TypeError('Expecting ISO8601 (Date constructor)');
|
||||
this.timestamp = timestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field to the embed (max 25).
|
||||
*/
|
||||
public addField(name: string, value: string, inline = false) {
|
||||
if (typeof name !== 'string') throw new TypeError('RichEmbed Field names must be a string.');
|
||||
if (typeof value !== 'string') throw new TypeError('RichEmbed Field values must be a string.');
|
||||
if (typeof inline !== 'boolean') throw new TypeError('RichEmbed Field inlines must be a boolean.');
|
||||
if (this.fields.length >= 25) throw new RangeError('RichEmbeds may not exceed 25 fields.');
|
||||
if (name.length > 256) throw new RangeError('RichEmbed field names may not exceed 256 characters.');
|
||||
if (!/\S/.test(name)) throw new RangeError('RichEmbed field names may not be empty.');
|
||||
if (value.length > 1024) throw new RangeError('RichEmbed field values may not exceed 1024 characters.');
|
||||
if (!/\S/.test(value)) throw new RangeError('RichEmbed field values may not be empty.');
|
||||
this.fields.push({ name, value, inline });
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function for `<RichEmbed>.addField('\u200B', '\u200B', inline)`.
|
||||
*/
|
||||
public addBlankField(inline = false) {
|
||||
return this.addField('\u200B', '\u200B', inline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the thumbnail of this embed.
|
||||
*/
|
||||
public setThumbnail(url: string) {
|
||||
if (typeof url !== 'string') throw new TypeError('RichEmbed Thumbnail URLs must be a string.');
|
||||
this.thumbnail = { url };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the image of this embed.
|
||||
*/
|
||||
public setImage(url: string) {
|
||||
if (typeof url !== 'string') throw new TypeError('RichEmbed Image URLs must be a string.');
|
||||
if (!url.startsWith('http://') || !url.startsWith('https://')) url = `https://${url}`;
|
||||
this.image = { url };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the footer of this embed.
|
||||
*/
|
||||
public setFooter(text: string, icon_url?: string) {
|
||||
if (typeof text !== 'string') throw new TypeError('RichEmbed Footers must be a string.');
|
||||
if (icon_url && typeof icon_url !== 'string') throw new TypeError('RichEmbed Footer icon URLs must be a string.');
|
||||
if (text.length > 2048) throw new RangeError('RichEmbed footer text may not exceed 2048 characters.');
|
||||
this.footer = { text, icon_url };
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/* eslint-disable consistent-return */
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { Server } from '.';
|
||||
|
||||
export default class Route {
|
||||
public server: Server;
|
||||
|
||||
public conf: { path: string; deprecated?: boolean; maintenance?: boolean; };
|
||||
|
||||
public router: Router;
|
||||
|
||||
constructor(server: Server) {
|
||||
this.server = server;
|
||||
this.conf = { path: '' };
|
||||
this.router = Router();
|
||||
}
|
||||
|
||||
public bind() {}
|
||||
|
||||
public init() {
|
||||
this.router.all('*', (req, res, next) => {
|
||||
this.server.client.util.signale.log(`'${req.method}' request from '${req.ip}' to '${req.hostname}${req.path}'.`);
|
||||
this.server.client.db.Stat.updateOne({ name: 'requests' }, { $inc: { value: 1 } }).exec();
|
||||
if (this.conf.maintenance === true) res.status(503).json({ code: this.constants.codes.MAINTENANCE_OR_UNAVAILABLE, message: this.constants.messages.MAINTENANCE_OR_UNAVAILABLE });
|
||||
else if (this.conf.deprecated === true) res.status(501).json({ code: this.constants.codes.DEPRECATED, message: this.constants.messages.DEPRECATED });
|
||||
else next();
|
||||
});
|
||||
}
|
||||
|
||||
public deprecated(): void {
|
||||
this.router.all('*', (_req, res) => {
|
||||
res.status(501).json({ code: this.constants.codes.DEPRECATED, message: this.constants.messages.DEPRECATED });
|
||||
});
|
||||
}
|
||||
|
||||
public maintenance(): void {
|
||||
this.router.all('*', (_req, res) => {
|
||||
res.status(503).json({ code: this.constants.codes.MAINTENANCE_OR_UNAVAILABLE, message: this.constants.messages.MAINTENANCE_OR_UNAVAILABLE });
|
||||
});
|
||||
}
|
||||
|
||||
public handleError(error: Error, res: Response) {
|
||||
res.status(500).json({ code: this.constants.codes.SERVER_ERROR, message: this.constants.messages.SERVER_ERROR });
|
||||
this.server.parent.client.util.handleError(error);
|
||||
}
|
||||
|
||||
get constants() {
|
||||
return {
|
||||
codes: {
|
||||
SUCCESS: 100,
|
||||
UNAUTHORIZED: 101,
|
||||
PERMISSION_DENIED: 104,
|
||||
ENDPOINT_NOT_FOUND: 104,
|
||||
NOT_FOUND: 1041,
|
||||
ACCOUNT_NOT_FOUND: 1041,
|
||||
CLIENT_ERROR: 1044,
|
||||
SERVER_ERROR: 105,
|
||||
DEPRECATED: 1051,
|
||||
MAINTENANCE_OR_UNAVAILABLE: 1053,
|
||||
},
|
||||
messages: {
|
||||
UNAUTHORIZED: ['CREDENTIALS_INVALID', 'The credentials you supplied are invalid.'],
|
||||
BEARER_TOKEN_INVALID: ['BEARER_TOKEN_INVALID', 'The Bearer token you supplied is invalid.'],
|
||||
PERMISSION_DENIED: ['PERMISSION_DENIED', 'You do not have valid credentials to access this resource.'],
|
||||
NOT_FOUND: ['NOT_FOUND', 'The resource you requested cannot be located.'],
|
||||
ENDPOINT_NOT_FOUND: ['ENDPOINT_NOT_FOUND', 'The endpoint you requested does not exist or cannot be located.'],
|
||||
CLIENT_ERROR: ['CLIENT_ERROR', 'The information provided to this endpoint via headers, body, query, or parameters are invalid.'],
|
||||
SERVER_ERROR: ['INTERNAL_ERROR', 'An internal error has occurred, Engineers have been notified.'],
|
||||
DEPRECATED: ['ENDPOINT_OR_RESOURCE_DEPRECATED', 'The endpoint or resource you\'re trying to access has been deprecated.'],
|
||||
MAINTENANCE_OR_UNAVAILABLE: ['SERVICE_UNAVAILABLE', 'The endpoint or resource you\'re trying to access is either in maintenance or is not available.'],
|
||||
},
|
||||
discord: {
|
||||
SERVER_ID: '446067825673633794',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import helmet from 'helmet';
|
||||
import { Server as HTTPServer } from 'http';
|
||||
import { Collection, ServerManagement, Route } from '.';
|
||||
|
||||
export default class Server {
|
||||
public app: express.Application;
|
||||
|
||||
public routes: Collection<Route>;
|
||||
|
||||
public parent: ServerManagement;
|
||||
|
||||
public port: number;
|
||||
|
||||
private root: string;
|
||||
|
||||
protected parse: boolean;
|
||||
|
||||
constructor(parent: ServerManagement, port: number, routeRoot: string, parse = true) {
|
||||
this.parent = parent;
|
||||
this.app = express();
|
||||
this.routes = new Collection<Route>();
|
||||
this.port = port;
|
||||
this.root = routeRoot;
|
||||
|
||||
this.parse = parse;
|
||||
|
||||
this.init();
|
||||
this.loadRoutes();
|
||||
}
|
||||
|
||||
get client() {
|
||||
return this.parent.client;
|
||||
}
|
||||
|
||||
public async loadRoutes() {
|
||||
const routes = Object.values<typeof Route>(require(this.root));
|
||||
for (const RouteFile of routes) {
|
||||
const route = new RouteFile(this);
|
||||
if (route.conf.deprecated) {
|
||||
route.deprecated();
|
||||
} else if (route.conf.maintenance) {
|
||||
route.maintenance();
|
||||
} else {
|
||||
route.init();
|
||||
route.bind();
|
||||
}
|
||||
this.parent.client.util.signale.success(`Successfully loaded route 'http://localhost:${this.port}/${route.conf.path}'.`);
|
||||
this.routes.add(route.conf.path, route);
|
||||
this.app.use(route.conf.path, route.router);
|
||||
}
|
||||
this.app.listen(this.port);
|
||||
}
|
||||
|
||||
public init() {
|
||||
if (this.parse) {
|
||||
this.app.use(bodyParser.json());
|
||||
this.app.use(bodyParser.urlencoded({ extended: true }));
|
||||
}
|
||||
this.app.set('trust proxy', 'loopback');
|
||||
this.app.use(helmet({
|
||||
hsts: false,
|
||||
hidePoweredBy: false,
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
},
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
public listen(port: number): HTTPServer {
|
||||
return this.app.listen(port);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { Client, Collection, Server } from '.';
|
||||
import serverSetup from '../api';
|
||||
|
||||
export default class ServerManagement {
|
||||
public client: Client;
|
||||
|
||||
public servers: Collection<Server>;
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
this.servers = new Collection<Server>();
|
||||
this.loadServers();
|
||||
}
|
||||
|
||||
public async loadServers() {
|
||||
const apiRoot = Object.entries<(management: ServerManagement) => Server>(serverSetup);
|
||||
for (const [api, server] of apiRoot) {
|
||||
this.servers.add(api, server(this));
|
||||
this.client.util.signale.success(`Successfully loaded server '${api}'.`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,310 @@
|
|||
/* eslint-disable no-bitwise */
|
||||
import nodemailer from 'nodemailer';
|
||||
import childProcess from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import signale from 'signale';
|
||||
import { Member, Message, Guild, PrivateChannel, GroupChannel, Role, AnyGuildChannel, WebhookPayload } from 'eris';
|
||||
import { Client, Command, Moderation, PBX, Report, RichEmbed } from '.';
|
||||
import { statusMessages as emotes } from '../configs/emotes.json';
|
||||
|
||||
export default class Util {
|
||||
public client: Client;
|
||||
|
||||
public moderation: Moderation;
|
||||
|
||||
public signale: signale.Signale;
|
||||
|
||||
public transporter: nodemailer.Transporter;
|
||||
|
||||
public pbx: PBX;
|
||||
|
||||
public report: Report;
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
this.moderation = new Moderation(this.client);
|
||||
this.signale = signale;
|
||||
this.signale.config({
|
||||
displayDate: true,
|
||||
displayTimestamp: true,
|
||||
displayFilename: true,
|
||||
});
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host: 'staff.libraryofcode.org',
|
||||
port: 587,
|
||||
auth: { user: 'internal', pass: this.client.config.emailPass },
|
||||
});
|
||||
this.pbx = new PBX(this.client);
|
||||
this.report = new Report(this.client);
|
||||
}
|
||||
|
||||
get emojis() {
|
||||
return {
|
||||
SUCCESS: emotes.success,
|
||||
LOADING: emotes.loading,
|
||||
ERROR: emotes.error,
|
||||
};
|
||||
}
|
||||
|
||||
public hrn(number: any, fixed: number, formatter: any | any[]) {
|
||||
const builtInFormatters = {
|
||||
en: ['KMGTPEZY'.split(''), 1e3],
|
||||
zh_CN: ['百千万亿兆京垓秭'.split(''), [100, 10, 10, 1e4, 1e4, 1e4, 1e4, 1e4]],
|
||||
};
|
||||
number = Math.abs(number);
|
||||
if (!fixed && fixed !== 0) fixed = 1;
|
||||
|
||||
if (typeof formatter === 'object') {
|
||||
const name = `${new Date().getTime()}`;
|
||||
builtInFormatters[name] = formatter;
|
||||
formatter = name;
|
||||
}
|
||||
|
||||
if (!builtInFormatters[formatter]) formatter = 'en';
|
||||
|
||||
formatter = builtInFormatters[formatter];
|
||||
let power = 1;
|
||||
const texts = formatter[0];
|
||||
const powers = formatter[1];
|
||||
let loop = 0;
|
||||
let is_array = false;
|
||||
|
||||
if (typeof powers === 'object') is_array = true;
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (1) {
|
||||
if (is_array) power = powers[loop];
|
||||
else power = powers;
|
||||
|
||||
if (number >= power && loop < texts.length) number /= power;
|
||||
else {
|
||||
number = number.toFixed(fixed);
|
||||
return loop === 0 ? number : `${number} ${texts[loop - 1]}`;
|
||||
}
|
||||
// eslint-disable-next-line no-plusplus
|
||||
++loop;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a command
|
||||
* @param query Command input
|
||||
* @param message Only used to check for errors
|
||||
*/
|
||||
/* public resolveCommand(query: string | string[]): Promise<{cmd: Command, args: string[] }> {
|
||||
try {
|
||||
if (typeof query === 'string') query = query.split(' ');
|
||||
const commands = this.client.commands.toArray();
|
||||
const resolvedCommand = commands.find((c) => c.name === query[0].toLowerCase() || c.aliases.includes(query[0].toLowerCase()));
|
||||
|
||||
if (!resolvedCommand) return Promise.resolve(null);
|
||||
query.shift();
|
||||
return Promise.resolve({ cmd: resolvedCommand, args: query });
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public dataConversion(bytes: number): string {
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
if (bytes === 0) {
|
||||
return '0 KB';
|
||||
}
|
||||
return `${(bytes / 1024 ** i).toFixed(2)} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
public async exec(command: string, _options: childProcess.ExecOptions = {}): Promise<string> {
|
||||
const ex = promisify(childProcess.exec);
|
||||
try {
|
||||
return (await ex(command)).stdout;
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
/* return new Promise((res, rej) => {
|
||||
let output = '';
|
||||
const writeFunction = (data: string|Buffer|Error) => {
|
||||
output += `${data}`;
|
||||
};
|
||||
const cmd = childProcess.exec(command, options);
|
||||
cmd.stdout.on('data', writeFunction);
|
||||
cmd.stderr.on('data', writeFunction);
|
||||
cmd.on('error', writeFunction);
|
||||
cmd.once('close', (code, signal) => {
|
||||
cmd.stdout.off('data', writeFunction);
|
||||
cmd.stderr.off('data', writeFunction);
|
||||
cmd.off('error', writeFunction);
|
||||
setTimeout(() => {}, 1000);
|
||||
if (code !== 0) rej(new Error(`Command failed: ${command}\n${output}`));
|
||||
res(output);
|
||||
});
|
||||
}); */
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a command
|
||||
* @param query Command input
|
||||
* @param message Only used to check for errors
|
||||
*/
|
||||
public resolveCommand(query: string | string[], message?: Message): Promise<{ cmd: Command, args: string[] }> {
|
||||
try {
|
||||
let resolvedCommand: Command;
|
||||
if (typeof query === 'string') query = query.split(' ');
|
||||
const commands = this.client.commands.toArray();
|
||||
resolvedCommand = commands.find((c) => c.name === query[0].toLowerCase() || c.aliases.includes(query[0].toLowerCase()));
|
||||
|
||||
if (!resolvedCommand) return Promise.resolve(null);
|
||||
query.shift();
|
||||
while (resolvedCommand.subcommands.size && query.length) {
|
||||
const subCommands = resolvedCommand.subcommands.toArray();
|
||||
const found = subCommands.find((c) => c.name === query[0].toLowerCase() || c.aliases.includes(query[0].toLowerCase()));
|
||||
if (!found) break;
|
||||
resolvedCommand = found;
|
||||
query.shift();
|
||||
}
|
||||
return Promise.resolve({ cmd: resolvedCommand, args: query });
|
||||
} catch (error) {
|
||||
if (message) this.handleError(error, message);
|
||||
else this.handleError(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
public resolveGuildChannel(query: string, { channels }: Guild, categories = false): AnyGuildChannel | undefined {
|
||||
const ch: AnyGuildChannel[] = channels.filter((c) => (!categories ? c.type !== 4 : true));
|
||||
return ch.find((c) => c.id === query.replace(/[<#>]/g, '') || c.name === query)
|
||||
|| ch.find((c) => c.name.toLowerCase() === query.toLowerCase())
|
||||
|| ch.find((c) => c.name.toLowerCase().startsWith(query.toLowerCase()));
|
||||
}
|
||||
|
||||
public resolveRole(query: string, { roles }: Guild): Role | undefined {
|
||||
return roles.find((r) => r.id === query.replace(/[<@&>]/g, '') || r.name === query)
|
||||
|| roles.find((r) => r.name.toLowerCase() === query.toLowerCase())
|
||||
|| roles.find((r) => r.name.toLowerCase().startsWith(query.toLowerCase()));
|
||||
}
|
||||
|
||||
public resolveMember(query: string, { members }: Guild): Member | undefined {
|
||||
return members.find((m) => `${m.username}#${m.discriminator}` === query || m.username === query || m.id === query.replace(/[<@!>]/g, '') || m.nick === query) // Exact match for mention, username+discrim, username and user ID
|
||||
|| members.find((m) => `${m.username.toLowerCase()}#${m.discriminator}` === query.toLowerCase() || m.username.toLowerCase() === query.toLowerCase() || (m.nick && m.nick.toLowerCase() === query.toLowerCase())) // Case insensitive match for username+discrim, username
|
||||
|| members.find((m) => m.username.toLowerCase().startsWith(query.toLowerCase()) || (m.nick && m.nick.toLowerCase().startsWith(query.toLowerCase())));
|
||||
}
|
||||
|
||||
public async handleError(error: Error, message?: Message, command?: Command, disable = true): Promise<void> {
|
||||
try {
|
||||
this.signale.error(error);
|
||||
const info: WebhookPayload = { content: `\`\`\`js\n${error.stack || error}\n\`\`\``, embeds: [] };
|
||||
if (message) {
|
||||
const embed = new RichEmbed();
|
||||
embed.setColor('FF0000');
|
||||
embed.setAuthor(`Error caused by ${message.author.username}#${message.author.discriminator}`, message.author.avatarURL);
|
||||
embed.setTitle('Message content');
|
||||
embed.setDescription(message.content);
|
||||
embed.addField('User', `${message.author.mention} (\`${message.author.id}\`)`, true);
|
||||
embed.addField('Channel', message.channel.mention, true);
|
||||
let guild: string;
|
||||
if (message.channel instanceof PrivateChannel || message.channel instanceof GroupChannel) guild = '@me';
|
||||
else guild = message.channel.guild.id;
|
||||
embed.addField('Message link', `[Click here](https://discordapp.com/channels/${guild}/${message.channel.id}/${message.id})`, true);
|
||||
embed.setTimestamp(new Date(message.timestamp));
|
||||
info.embeds.push(embed);
|
||||
}
|
||||
await this.client.executeWebhook(this.client.config.webhookID, this.client.config.webhookToken, info);
|
||||
const msg = message ? message.content.slice(this.client.config.prefix.length).trim().split(/ +/g) : [];
|
||||
if (command && disable) this.resolveCommand(msg).then((c) => { c.cmd.enabled = false; });
|
||||
if (message) message.channel.createMessage(`***${this.emojis.ERROR} An unexpected error has occured - please contact a Staff member.${command && disable ? ' This command has been disabled.' : ''}***`);
|
||||
} catch (err) {
|
||||
this.signale.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
public splitString(string: string, length: number): string[] {
|
||||
if (!string) return [];
|
||||
if (Array.isArray(string)) string = string.join('\n');
|
||||
if (string.length <= length) return [string];
|
||||
const arrayString: string[] = [];
|
||||
let str: string = '';
|
||||
let pos: number;
|
||||
while (string.length > 0) {
|
||||
pos = string.length > length ? string.lastIndexOf('\n', length) : string.length;
|
||||
if (pos > length) pos = length;
|
||||
str = string.substr(0, pos);
|
||||
string = string.substr(pos);
|
||||
arrayString.push(str);
|
||||
}
|
||||
return arrayString;
|
||||
}
|
||||
|
||||
public splitFields(fields: { name: string, value: string, inline?: boolean }[]): { name: string, value: string, inline?: boolean }[][] {
|
||||
let index = 0;
|
||||
const array: { name: string, value: string, inline?: boolean }[][] = [[]];
|
||||
while (fields.length) {
|
||||
if (array[index].length >= 25) { index += 1; array[index] = []; }
|
||||
array[index].push(fields[0]); fields.shift();
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public splitArray<T>(array: T[], count: number) {
|
||||
const finalArray: T[][] = [];
|
||||
while (array.length) {
|
||||
finalArray.push(array.splice(0, count));
|
||||
}
|
||||
return finalArray;
|
||||
}
|
||||
|
||||
public decimalToHex(int: number): string {
|
||||
const hex = int.toString(16);
|
||||
return '#000000'.substring(0, 7 - hex.length) + hex;
|
||||
}
|
||||
|
||||
public randomNumber(min: number, max: number): number {
|
||||
return Math.round(Math.random() * (max - min) + min);
|
||||
}
|
||||
|
||||
public encode(arg: string) {
|
||||
return arg.split('').map((x) => x.charCodeAt(0) / 400);
|
||||
}
|
||||
|
||||
public normalize(string) {
|
||||
const input = [];
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
input.push(string.charCodeAt(i) / 1000);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
public convert_ascii(ascii: []) {
|
||||
let string = '';
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let i = 0; i < ascii.length; i++) {
|
||||
string += String.fromCharCode(ascii[i] * 4000);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
public percentile(arr: number[], val: number) {
|
||||
return (100 * arr.reduce((acc, v) => acc + (v < val ? 1 : 0) + (v === val ? 0.5 : 0), 0)) / arr.length;
|
||||
}
|
||||
|
||||
public ordinal(i: number) {
|
||||
const j = i % 10;
|
||||
const k = i % 100;
|
||||
if (j === 1 && k !== 11) {
|
||||
return `${i}st`;
|
||||
}
|
||||
if (j === 2 && k !== 12) {
|
||||
return `${i}nd`;
|
||||
}
|
||||
if (j === 3 && k !== 13) {
|
||||
return `${i}rd`;
|
||||
}
|
||||
return `${i}th`;
|
||||
}
|
||||
|
||||
public capsFirstLetter(string?: string): string | void {
|
||||
if (typeof string !== 'string') return undefined;
|
||||
return string.substring(0, 1).toUpperCase() + string.substring(1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
export { default as Client } from './Client';
|
||||
export { default as Collection } from './Collection';
|
||||
export { default as Command } from './Command';
|
||||
export { default as Event } from './Event';
|
||||
export { default as Handler } from './Handler';
|
||||
export { default as LocalStorage } from './LocalStorage';
|
||||
export { default as Moderation } from './Moderation';
|
||||
export { default as PBX } from './PBX';
|
||||
export { default as Queue } from './Queue';
|
||||
export { default as Report } from './Report';
|
||||
export { default as RichEmbed } from './RichEmbed';
|
||||
export { default as Route } from './Route';
|
||||
export { default as Server } from './Server';
|
||||
export { default as ServerManagement } from './ServerManagement';
|
||||
export { default as Util } from './Util';
|
|
@ -0,0 +1,60 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class AddItem extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'additem';
|
||||
this.description = 'Adds information to your whois embed.';
|
||||
this.usage = 'additem [code]';
|
||||
this.permissions = 0;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (args.length < 1) {
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Whois Data Codes');
|
||||
embed.addField('Languages', '**Assembly Language:** lang-asm\n**C/C++:** lang-cfam\n**C#:** lang-csharp\n**Go:** lang-go\n**Java:** lang-java\n**JavaScript:** lang-js\n**Kotlin:** lang-kt\n**Python:** lang-py\n**Ruby:** lang-rb\n**Rust:** lang-rs\n**Swift:** lang-swift\n**TypeScript:** lang-ts');
|
||||
embed.addField('Operating Systems', '**Arch:** os-arch\n**Debian:** os-deb\n**CentOS:** os-cent\n**Fedora:** os-fedora\n**macOS:** os-mdarwin\n**Manjaro:** os-manjaro\n**RedHat:** os-redhat\n**Ubuntu:** os-ubuntu\n**Windows:** os-win');
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
return message.channel.createMessage({ embed });
|
||||
}
|
||||
if (args[0].split('-')[0] === 'os' && ['arch', 'deb', 'cent', 'fedora', 'manjaro', 'mdarwin', 'redhat', 'ubuntu', 'win'].includes(args[0].split('-')[1])) {
|
||||
const account = await this.client.db.Member.findOne({ userID: message.member.id });
|
||||
if (!account) {
|
||||
const newAccount = new this.client.db.Member({
|
||||
userID: message.member.id,
|
||||
additional: {
|
||||
operatingSystems: [args[0].split('-')[1]],
|
||||
},
|
||||
});
|
||||
await newAccount.save();
|
||||
} else {
|
||||
await account.updateOne({ $addToSet: { 'additional.operatingSystems': args[0].split('-')[1] } });
|
||||
}
|
||||
return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Added OS code ${args[0]} to profile.***`);
|
||||
}
|
||||
if (args[0].split('-')[0] === 'lang' && ['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'].includes(args[0].split('-')[1])) {
|
||||
const account = await this.client.db.Member.findOne({ userID: message.member.id });
|
||||
if (!account) {
|
||||
const newAccount = new this.client.db.Member({
|
||||
userID: message.member.id,
|
||||
additional: {
|
||||
langs: [args[0].split('-')[1]],
|
||||
},
|
||||
});
|
||||
await newAccount.save();
|
||||
} else {
|
||||
await account.updateOne({ $addToSet: { 'additional.langs': args[0].split('-')[1] } });
|
||||
}
|
||||
return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Added language code ${args[0]} to profile.***`);
|
||||
}
|
||||
return message.channel.createMessage(`***${this.client.util.emojis.ERROR} Invalid data code.***`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import { Message } from 'eris';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class AddMerchant extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'addmerchant';
|
||||
this.description = 'Creates a new merchant.';
|
||||
this.usage = `${this.client.config.prefix}addmerchant <privileged: 1 for yes | 0 for no> <type: 0 for only soft | 1 for soft and hard> <merchant name>`;
|
||||
this.aliases = ['am'];
|
||||
this.permissions = 6;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[1]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
if ((Number(args[0]) !== 0) && (Number(args[0]) !== 1)) return this.error(message.channel, 'Invalid permissions.');
|
||||
if ((Number(args[1]) !== 0) && (Number(args[1]) !== 1)) return this.error(message.channel, 'Invalid permissions.');
|
||||
const key = randomBytes(20).toString('hex');
|
||||
const merchant = await (new this.client.db.Merchant({
|
||||
name: args.slice(2).join(' '),
|
||||
privileged: Number(args[0]),
|
||||
type: Number(args[1]),
|
||||
key,
|
||||
pulls: [],
|
||||
})).save();
|
||||
return this.success(message.channel, `Created merchant (${merchant._id}). \`${args.slice(2).join(' ')}\`\n\n\`${key}\``);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import { Message } from 'eris';
|
||||
import { Command, Client } from '../class';
|
||||
|
||||
export default class AddNote extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'addnote';
|
||||
this.description = 'Adds a note to a member.';
|
||||
this.usage = `${this.client.config.prefix}addnote <member> <text> [category: comm | cs | edu]`;
|
||||
this.permissions = 1;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0] || args.length < 1) return this.client.commands.get('help').run(message, [this.name]);
|
||||
let user = this.client.util.resolveMember(args[0], this.mainGuild)?.user;
|
||||
if (!user) user = await this.client.getRESTUser(args[0]);
|
||||
if (!user) return this.error(message.channel, 'The member you specified could not be found.');
|
||||
|
||||
const note: { userID?: string, staffID: string, date: Date, category?: string, text?: string } = {
|
||||
userID: user.id,
|
||||
date: new Date(),
|
||||
staffID: message.author.id,
|
||||
};
|
||||
if (args[args.length - 1] !== 'edu' && args[args.length - 1] !== 'comm' && args[args.length - 1] !== 'cs') {
|
||||
note.category = '';
|
||||
note.text = args.slice(1).join(' ');
|
||||
} else {
|
||||
note.category = args[args.length - 1];
|
||||
note.text = args.slice(0, args.length - 1).join(' ');
|
||||
}
|
||||
const saved = await (new this.client.db.Note(note).save());
|
||||
return this.success(message.channel, `Successfully created Note # \`${saved._id}\`.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class AddRank extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'addrank';
|
||||
this.description = 'Makes a role self-assignable.';
|
||||
this.usage = `${this.client.config.prefix}addrank <role> <permissions, pass 0 for everyone. separate role IDs with ':'> <description>`;
|
||||
this.permissions = 6;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
if (!args[1]) return this.error(message.channel, 'Permissions are required.');
|
||||
if (!args[2]) return this.error(message.channel, 'A description is required');
|
||||
const role = this.client.util.resolveRole(args[0], this.mainGuild);
|
||||
if (!role) return this.error(message.channel, 'The role you specified doesn\'t appear to exist.');
|
||||
|
||||
const check = await this.client.db.Rank.findOne({ roleID: role.id });
|
||||
if (check) return this.error(message.channel, 'This role is already self-assignable.');
|
||||
|
||||
let permissions: string[];
|
||||
if (args[1] === '0') {
|
||||
permissions = ['0'];
|
||||
} else {
|
||||
permissions = args[1].split(':');
|
||||
}
|
||||
|
||||
const entry = new this.client.db.Rank({
|
||||
name: role.name,
|
||||
roleID: role.id,
|
||||
permissions,
|
||||
description: args.slice(2).join(' '),
|
||||
});
|
||||
await entry.save();
|
||||
return this.success(message.channel, `Role ${role.name} is now self-assignable.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class AddRedirect extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'addredirect';
|
||||
this.description = 'Adds a redirect link for \'loc.sh\'';
|
||||
this.usage = 'addredirect <redirect to url> <key>';
|
||||
this.aliases = ['ar'];
|
||||
this.permissions = 6;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const check = await this.client.db.Redirect.findOne({ key: args[1].toLowerCase() });
|
||||
if (check) return this.error(message.channel, `Redirect key ${args[1].toLowerCase()} already exists. Linked to: ${check.to}`);
|
||||
try {
|
||||
const test = new URL(args[0]);
|
||||
if (test.protocol !== 'https:') return this.error(message.channel, 'Protocol must be HTTPS.');
|
||||
} catch {
|
||||
return this.error(message.channel, 'This doesn\'t appear to be a valid URL.');
|
||||
}
|
||||
if ((/^[a-zA-Z0-9]+$/gi.test(args[1].toLowerCase().replace('-', '').trim()) === false) || args[1].toLowerCase().length > 15) return this.error(message.channel, 'Invalid key. The key must be alphanumeric and less than 16 characters.');
|
||||
const redirect = new this.client.db.Redirect({
|
||||
key: args[1].toLowerCase(),
|
||||
to: args[0],
|
||||
visitedCount: 0,
|
||||
});
|
||||
await redirect.save();
|
||||
return this.success(message.channel, `Redirect https://loc.sh/${args[1].toLowerCase()} -> ${args[0]} is now active.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
/* eslint-disable no-continue */
|
||||
import type { AxiosError, AxiosStatic } from 'axios';
|
||||
import axios from 'axios';
|
||||
import { Member, Message } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class Apply extends Command {
|
||||
public services: Map<string, { description: string, type: 'HARD' | 'SOFT', url: string, validation: (...cond: any) => Promise<boolean> | boolean, func?: Function }>;
|
||||
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'apply';
|
||||
this.description = 'apply';
|
||||
this.usage = `${this.client.config.prefix}apply [serviceName]\n${this.client.config.prefix}apply full`;
|
||||
this.permissions = 0;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
this.setServices();
|
||||
}
|
||||
|
||||
protected setServices() {
|
||||
this.services = new Map();
|
||||
this.services.set('role::constants', {
|
||||
description: 'Constants role assignment.',
|
||||
type: 'HARD',
|
||||
url: 'https://eds.libraryofcode.org/roles/constants',
|
||||
validation: (member: Member) => !member.roles.includes('511771731891847168'),
|
||||
func: async (client: Client, ...data: any[]) => {
|
||||
const member = await client.guilds.get(client.config.guildID).getRESTMember(data[0]);
|
||||
await member.addRole('511771731891847168', 'Constants Approval from EDS');
|
||||
},
|
||||
});
|
||||
|
||||
this.services.set('cs::t2', {
|
||||
description: 'Tier 2 upgrade for Cloud Services account.',
|
||||
type: 'HARD',
|
||||
url: 'https://eds.libraryofcode.org/cs/t2',
|
||||
validation: (member: Member) => member.roles.includes('546457886440685578'),
|
||||
func: async (client: Client, ...data: any[]) => {
|
||||
const member = await client.guilds.get(client.config.guildID).getRESTMember(data[0]);
|
||||
const ax = <AxiosStatic>require('axios');
|
||||
await ax({
|
||||
method: 'get',
|
||||
url: `https://api.cloud.libraryofcode.org/wh/t2?userID=${member.id}&auth=${client.config.internalKey}`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
this.services.set('cs::promot3', {
|
||||
description: 'Receive 25% off your first purchase of Tier 3.',
|
||||
type: 'SOFT',
|
||||
url: 'https://eds.libraryofcode.org/cs/t3-promo',
|
||||
validation: async (member: Member) => {
|
||||
if (!member.roles.includes('546457886440685578')) return false;
|
||||
const customer = await this.client.db.Customer.findOne({ userID: member.user.id }).lean().exec();
|
||||
if (!customer) return false;
|
||||
return true;
|
||||
},
|
||||
func: async (client: Client, ...data: any[]) => {
|
||||
const member = await client.guilds.get(client.config.guildID).getRESTMember(data[0]);
|
||||
const customer = await client.db.Customer.findOne({ userID: member.user.id }).lean().exec();
|
||||
const coupon = await client.stripe.coupons.create({
|
||||
percent_off: 25,
|
||||
duration: 'once',
|
||||
max_redemptions: 1,
|
||||
name: 'Tier 3 - EDS Discount',
|
||||
metadata: {
|
||||
userID: member.user.id,
|
||||
},
|
||||
});
|
||||
const promo = await client.stripe.promotionCodes.create({
|
||||
coupon: coupon.id,
|
||||
customer: customer.cusID,
|
||||
max_redemptions: 1,
|
||||
restrictions: {
|
||||
first_time_transaction: true,
|
||||
},
|
||||
});
|
||||
const doc = new client.db.Promo({
|
||||
code: promo.code,
|
||||
pID: promo.id,
|
||||
});
|
||||
await doc.save();
|
||||
const chan = await client.getDMChannel(customer.userID);
|
||||
chan.createMessage(`__**Tier 3 Coupon Code**__\n\`${promo.code}\`\n\n*Do not share this promotional code with anyone else. This promo code is good for your first purchase of Tier 2, 25% off applied. Will apply to your first invoice only, for more questions contact support.*`);
|
||||
},
|
||||
});
|
||||
|
||||
this.services.set('p::role::constants', {
|
||||
description: 'Pre-approval for Constants role assignment.',
|
||||
type: 'SOFT',
|
||||
url: 'https://eds.libraryofcode.org/roles/preconstants',
|
||||
validation: (member: Member) => !member.roles.includes('511771731891847168'),
|
||||
});
|
||||
|
||||
this.services.set('p::cs::t2', {
|
||||
description: 'Pre-approval for Tier 2.',
|
||||
type: 'SOFT',
|
||||
url: 'https://eds.libraryofcode.org/cs/t2pre',
|
||||
validation: (member: Member) => member.roles.includes('546457886440685578'),
|
||||
});
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0] || args[0] === 'full') {
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Instant Application Service [IAS]');
|
||||
embed.setColor('#556cd6');
|
||||
if (args[0] !== 'full') {
|
||||
embed.setDescription(`*These applications are specifically targeted to you based on validation conditions. Run \`${this.client.config.prefix}apply full\` for a full list of all applications.*`);
|
||||
embed.setThumbnail(message.member.avatarURL);
|
||||
embed.setAuthor(message.member.username, message.member.avatarURL);
|
||||
}
|
||||
for (const service of this.services) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const test = await service[1].validation(message.member);
|
||||
if (!test && args[0] !== 'full') continue;
|
||||
embed.addField(service[0], `**Description**: ${service[1].description}\n**Inquiry Type:** ${service[1].type}\n\n*Run \`${this.client.config.prefix}apply ${service[0]}\` to apply.*`);
|
||||
}
|
||||
if (embed.fields?.length <= 0) embed.setDescription(`*We have no offers for you at this time. To see a full list of offers, please run \`${this.client.config.prefix}apply full\`.*`);
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
return message.channel.createMessage({ embed });
|
||||
}
|
||||
|
||||
if (!this.services.has(args[0])) return this.error(message.channel, 'Invalid service/product name.');
|
||||
const service = this.services.get(args[0]);
|
||||
const test = await this.services.get(args[0]).validation(message.member);
|
||||
if (!test) return this.error(message.channel, 'A condition exists which prevents you from applying, please try again later.');
|
||||
const msg = await this.loading(message.channel, 'Thank you for submitting an application. We are currently processing it, you will be pinged here shortly with the decision.');
|
||||
return await this.client.queue.processApplication({ channelID: message.channel.id, guildID: this.mainGuild.id, messageID: msg.id }, service.url, message.author.id, service.func ? service.func.toString() : undefined);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
|
||||
public static async apply(client: Client, url: string, userID: string) {
|
||||
try {
|
||||
const { data } = await axios({
|
||||
method: 'get',
|
||||
url: `${url}?userID=${userID}&auth=${client.config.internalKey}`,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'SUCCESS',
|
||||
decision: data.decision,
|
||||
id: data.id,
|
||||
processedBy: data.processedBy,
|
||||
token: data.token,
|
||||
};
|
||||
} catch (err) {
|
||||
const error = <AxiosError>err;
|
||||
if (error.response?.status === 404 || error.response.status === 400 || error.response.status === 401) return { id: 'N/A', processedBy: 'N/A', status: 'CLIENT_ERROR', decision: 'PRE-DECLINED' };
|
||||
return { id: 'N/A', processedBy: 'N/A', status: 'SERVER_ERROR', decision: 'PRE-DECLINED' };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import moment, { unitOfTime } from 'moment';
|
||||
import { Message, User } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Ban extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'ban';
|
||||
this.description = 'Bans a member from the guild.';
|
||||
this.usage = 'ban <member> [time] [reason]';
|
||||
this.permissions = 3;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const member = this.client.util.resolveMember(args[0], this.mainGuild);
|
||||
let user: User;
|
||||
if (!member) {
|
||||
try {
|
||||
user = await this.client.getRESTUser(args[0]);
|
||||
} catch {
|
||||
return this.error(message.channel, 'Cannot find user.');
|
||||
}
|
||||
} else {
|
||||
user = member.user;
|
||||
}
|
||||
try {
|
||||
await this.mainGuild.getBan(args[0]);
|
||||
return this.error(message.channel, 'This user is already banned.');
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
if (member && !this.client.util.moderation.checkPermissions(member, message.member)) return this.error(message.channel, 'Permission Denied.');
|
||||
message.delete();
|
||||
|
||||
let momentMilliseconds: number;
|
||||
let reason: string;
|
||||
if (args.length > 1) {
|
||||
const lockLength = args[1].match(/[a-z]+|[^a-z]+/gi);
|
||||
const length = Number(lockLength[0]);
|
||||
const unit = lockLength[1] as unitOfTime.Base;
|
||||
momentMilliseconds = moment.duration(length, unit).asMilliseconds();
|
||||
reason = momentMilliseconds ? args.slice(2).join(' ') : args.slice(1).join(' ');
|
||||
if (reason.length > 512) return this.error(message.channel, 'Ban reasons cannot be longer than 512 characters.');
|
||||
}
|
||||
await this.client.util.moderation.ban(user, message.member, momentMilliseconds, reason);
|
||||
return this.success(message.channel, `${user.username}#${user.discriminator} has been banned.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
import { Message } from 'eris';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Client, Command } from '../class';
|
||||
import Billing_T3 from './billing_t3';
|
||||
|
||||
export default class Billing extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'billing';
|
||||
this.description = 'Pulls up your Billing Portal. You must have a CS Account to continue.';
|
||||
this.usage = `${this.client.config.prefix}billing`;
|
||||
this.subcmds = [Billing_T3];
|
||||
this.permissions = 0;
|
||||
this.guildOnly = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message) {
|
||||
try {
|
||||
const response = <{
|
||||
found: boolean,
|
||||
emailAddress?: string,
|
||||
tier?: number,
|
||||
supportKey?: string,
|
||||
}> (await axios.get(`https://api.cloud.libraryofcode.org/wh/info?id=${message.author.id}&authorization=${this.client.config.internalKey}`)).data;
|
||||
if (!response.found) return this.error(message.channel, 'CS Account not found.');
|
||||
|
||||
const portalKey = randomBytes(50).toString('hex');
|
||||
const uid = uuid();
|
||||
const redirect = new this.client.db.Redirect({
|
||||
key: uid,
|
||||
to: `https://loc.sh/dash?q=${portalKey}`,
|
||||
});
|
||||
const portal = new this.client.db.CustomerPortal({
|
||||
key: portalKey,
|
||||
username: message.author.username,
|
||||
userID: message.author.id,
|
||||
emailAddress: response.emailAddress,
|
||||
expiresOn: moment().add(5, 'minutes').toDate(),
|
||||
used: false,
|
||||
});
|
||||
await portal.save();
|
||||
await redirect.save();
|
||||
|
||||
const chan = await this.client.getDMChannel(message.author.id);
|
||||
await chan.createMessage(`__***Billing Account Portal***__\nClick here: https://loc.sh/${uid}\n\nYou will be redirected to your billing portal, please note the link expires after 5 minutes.`);
|
||||
return await this.success(message.channel, 'Your Billing Portal information has been DMed to you.');
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import axios from 'axios';
|
||||
import { Message } from 'eris';
|
||||
import type { Stripe } from 'stripe';
|
||||
import { Client, Command } from '../class';
|
||||
import type { PromoInterface } from '../models';
|
||||
|
||||
export default class Billing_T3 extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 't3';
|
||||
this.description = 'Subscription to CS Tier 3.';
|
||||
this.usage = `${this.client.config.prefix}billing t3 [promoCode]`;
|
||||
this.permissions = 0;
|
||||
this.guildOnly = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
await message.delete();
|
||||
const response = <{
|
||||
found: boolean,
|
||||
emailAddress?: string,
|
||||
tier?: number,
|
||||
supportKey?: string,
|
||||
}>(await axios.get(`https://api.cloud.libraryofcode.org/wh/info?id=${message.author.id}&authorization=${this.client.config.internalKey}`)).data;
|
||||
if (!response.found) return this.error(message.channel, 'CS Account not found.');
|
||||
|
||||
const customer = await this.client.db.Customer.findOne({ userID: message.author.id });
|
||||
if (!customer) return this.error(message.channel, `You do not have a Customer Account. Please run \`${this.client.config.prefix}billing\`, once you visit the Billing Portal via the URL given to you, please try again.`);
|
||||
|
||||
let promoCode: PromoInterface;
|
||||
if (args[0]) {
|
||||
promoCode = await this.client.db.Promo.findOne({ code: args[0].toUpperCase() });
|
||||
}
|
||||
|
||||
let subscription: Stripe.Response<Stripe.Subscription>;
|
||||
try {
|
||||
subscription = await this.client.stripe.subscriptions.create({
|
||||
customer: customer.cusID,
|
||||
payment_behavior: 'allow_incomplete',
|
||||
items: [{ price: 'price_1H8e6ODatwI1hQ4WFVvX6Nda' }],
|
||||
days_until_due: 1,
|
||||
collection_method: 'send_invoice',
|
||||
default_tax_rates: ['txr_1HlAadDatwI1hQ4WRHu14S2I'],
|
||||
promotion_code: promoCode ? promoCode.id : undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
return this.error(message.channel, `Error creating subscription.\n\n${err}`);
|
||||
}
|
||||
|
||||
await this.client.stripe.invoices.finalizeInvoice(subscription.latest_invoice.toString());
|
||||
const invoice = await this.client.stripe.invoices.retrieve(subscription.latest_invoice.toString());
|
||||
|
||||
const chan = await this.client.getDMChannel(message.author.id);
|
||||
await chan.createMessage(`__**Invoice for New Subscription**__\n${invoice.hosted_invoice_url}\n\n*Please click on the link above to pay for your subscription.*`);
|
||||
return this.success(message.channel, 'Transaction processed.');
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import PhoneNumber from 'awesome-phonenumber';
|
||||
import axios from 'axios';
|
||||
import { Message, TextChannel } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class Callback extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'callback';
|
||||
this.description = 'Requests a Callback from a Technican.\nPlease use `-` to separate the number if needed. E.x. 202-750-2585.\nDo note, we are unable to dial international numbers outside of the US and Canada.\n\n*We recommend you run this command in your DMs for privacy.*';
|
||||
this.usage = 'callback <the number you want us to call>';
|
||||
this.aliases = ['cb'];
|
||||
this.permissions = 0;
|
||||
this.guildOnly = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
if (message.channel.type === 0) await message.delete();
|
||||
const member = this.mainGuild.members.get(message.author.id);
|
||||
if (!member) return this.error(message.channel, 'Unable to fetch member.');
|
||||
const phone = new PhoneNumber(args.join(' '), 'US');
|
||||
if (!phone.isValid()) return this.error(message.channel, 'The number you have entered is invalid.');
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Callback Request');
|
||||
embed.setDescription('Please dial `9` first to reach an the external trunk. For example, to dial 202-750-2585 you would dial 92027502585 on your device.\n\n*Please react with <:modSuccess:578750988907970567> on this message if you are taking the call.*');
|
||||
embed.addField('Member', `${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`, true);
|
||||
embed.addField('Phone Number', phone.getNumber('national'), true);
|
||||
embed.addField('Phone Number Type', phone.getType(), true);
|
||||
const communityReport = await this.client.db.Score.findOne({ userID: message.author.id }).lean().exec();
|
||||
if (communityReport) {
|
||||
await this.client.report.createInquiry(member.user.id, 'Library of Code sp-us | VOIP/PBX Member Support SVCS', 1);
|
||||
embed.addField('PIN', `${communityReport.pin[0]}-${communityReport.pin[1]}-${communityReport.pin[2]}`, true);
|
||||
}
|
||||
try {
|
||||
const d = await axios.get(`https://api.cloud.libraryofcode.org/wh/info/?id=${message.author.id}&authorization=${this.client.config.internalKey}`);
|
||||
embed.addField('Email Address', d.data.emailAddress, true);
|
||||
embed.addField('Support Key', d.data.supportKey, true);
|
||||
} catch {
|
||||
this.client.util.signale.warn('No CS Account found for user.');
|
||||
}
|
||||
const chan = <TextChannel> this.mainGuild.channels.get('780513128240382002');
|
||||
const msg = await chan.createMessage({ content: '<@&780519428873781298>', embed });
|
||||
await msg.addReaction('modSuccess:578750988907970567');
|
||||
return message.channel.createMessage('__**Callback Request**__\nYour callback request has been sent to our agents, you should receive a callback within 1-24 hours from the date of this request. The number you will be called from is listed below.\n\n**Callback Number:** +1 202-750-2585\n**Callback Region:** Washington, D.C., United States\n\n\n*Your number is never stored on our systems at any time, as soon as your call is taken by an agent your number is deleted from notification channels.*');
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class DelItem extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'delitem';
|
||||
this.description = 'Removes information to your whois embed.';
|
||||
this.usage = 'delitem [code]';
|
||||
this.permissions = 0;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (args.length < 1) {
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Whois Data Codes');
|
||||
embed.addField('Languages', '**Assembly Language:** lang-asm\n**C/C++:** lang-cfam\n**C#:** lang-csharp\n**Go:** lang-go\n**Java:** lang-java\n**JavaScript:** lang-js\n**Kotlin:** lang-kt\n**Python:** lang-py\n**Ruby:** lang-rb\n**Rust:** lang-rs\n**Swift:** lang-swift\n**TypeScript:** lang-ts');
|
||||
embed.addField('Operating Systems', '**Arch:** os-arch\n**Debian:** os-deb\n**CentOS:** os-cent\n**Fedora:** os-fedora\n**macOS:** os-mdarwin\n**Manjaro:** os-manjaro\n**RedHat:** os-redhat\n**Ubuntu:** os-ubuntu\n**Windows:** os-win');
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
return message.channel.createMessage({ embed });
|
||||
}
|
||||
if (args[0].split('-')[0] === 'os' && ['arch', 'deb', 'cent', 'fedora', 'manjaro', 'mdarwin', 'redhat', 'ubuntu', 'win'].includes(args[0].split('-')[1])) {
|
||||
const account = await this.client.db.Member.findOne({ userID: message.member.id });
|
||||
if (account?.additional.operatingSystems.length < 1) {
|
||||
return message.channel.createMessage(`***${this.client.util.emojis.ERROR} You don't have any operating systems to remove.***`);
|
||||
}
|
||||
await account.updateOne({ $pull: { 'additional.operatingSystems': args[0].split('-')[1] } });
|
||||
return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Removed OS code ${args[0]} from profile.***`);
|
||||
}
|
||||
if (args[0].split('-')[0] === 'lang' && ['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'].includes(args[0].split('-')[1])) {
|
||||
const account = await this.client.db.Member.findOne({ userID: message.member.id });
|
||||
if (account?.additional.langs.length < 1) {
|
||||
return message.channel.createMessage(`***${this.client.util.emojis.ERROR} You don't have any languages to remove.***`);
|
||||
}
|
||||
await account.updateOne({ $pull: { 'additional.langs': args[0].split('-')[1] } });
|
||||
return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Removed language code ${args[0]} from profile.***`);
|
||||
}
|
||||
return message.channel.createMessage(`***${this.client.util.emojis.ERROR} Invalid data code.***`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class DelMerchant extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'delmerchant';
|
||||
this.description = 'Deletes a merchant.';
|
||||
this.usage = `${this.client.config.prefix}delmerchant <merchant id>`;
|
||||
this.aliases = ['dm'];
|
||||
this.permissions = 6;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const merchant = await this.client.db.Merchant.findOne({ key: args[0] });
|
||||
if (!merchant) return this.error(message.channel, 'Merchant specified does not exist.');
|
||||
return this.success(message.channel, `Deleted merchant \`${merchant._id}\`.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { Message } from 'eris';
|
||||
import { Command, Client } from '../class';
|
||||
|
||||
export default class DelNote extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'delnote';
|
||||
this.description = 'Deletes a note.';
|
||||
this.usage = `${this.client.config.prefix}delnote <note id>`;
|
||||
this.permissions = 1;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const note = await this.client.db.Note.findOne({ _id: args[0] }).lean().exec().catch(() => {});
|
||||
if (!note) return this.error(message.channel, 'Could not locate that note.');
|
||||
await this.client.db.Note.deleteOne({ _id: note._id });
|
||||
return this.success(message.channel, `Note # \`${note._id}\` has been deleted.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class DelRank extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'delrank';
|
||||
this.description = 'Deletes an existing self-assignable role. This doesn\'t delete the role itself.';
|
||||
this.usage = `${this.client.config.prefix}delrank <role>`;
|
||||
this.permissions = 6;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const role = this.client.util.resolveRole(args[0], this.mainGuild);
|
||||
if (!role) return this.error(message.channel, 'The role you specified doesn\'t appear to exist.');
|
||||
|
||||
const check = await this.client.db.Rank.findOne({ roleID: role.id });
|
||||
if (!check) return this.error(message.channel, 'The entry doesn\'t appear to exist.');
|
||||
|
||||
await this.client.db.Rank.deleteOne({ roleID: role.id });
|
||||
return this.success(message.channel, `Role ${role.name} is no longer self-assignable.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class DelRedirect extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'delredirect';
|
||||
this.description = 'Delete a redirect link for \'loc.sh\'';
|
||||
this.usage = 'delredirect <key>';
|
||||
this.aliases = ['dr'];
|
||||
this.permissions = 6;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const check = await this.client.db.Redirect.findOne({ key: args[0].toLowerCase() });
|
||||
if (!check) return this.error(message.channel, `Redirect key ${args[0].toLowerCase()} doesn't exist.`);
|
||||
await this.client.db.Redirect.deleteOne({ key: args[0].toLowerCase() });
|
||||
return this.success(message.channel, `Deleted redirect https://loc.sh/${args[0].toLowerCase()}.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import { Message, EmbedOptions } from 'eris';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class DJS extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'djs';
|
||||
this.description = 'Get information about Discord.js.';
|
||||
this.usage = 'djs <query>';
|
||||
this.permissions = 0;
|
||||
this.guildOnly = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
|
||||
let res: AxiosResponse<EmbedOptions>;
|
||||
try {
|
||||
res = await axios.get(`https://djsdocs.sorta.moe/v2/embed?src=master&q=${args[0]}`);
|
||||
} catch (err) {
|
||||
return this.error(message.channel, 'Please try again later, something unexpected happened.');
|
||||
}
|
||||
|
||||
if (!res.data) return this.error(message.channel, 'Could not find information. Try something else.');
|
||||
|
||||
const embed = new RichEmbed(res.data);
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
return message.channel.createMessage({ embed });
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { Message, EmbedOptions } from 'eris';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Eris extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'eris';
|
||||
this.description = 'Get information about Eris.';
|
||||
this.usage = 'eris <query>';
|
||||
this.permissions = 0;
|
||||
this.guildOnly = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
|
||||
let res: AxiosResponse<{embed: EmbedOptions}>;
|
||||
try {
|
||||
res = await axios.get('https://erisdocs.cloud.libraryofcode.org/docs', { params: { search: args[0] } });
|
||||
} catch (err) {
|
||||
if (err.code === 404) return this.error(message.channel, 'Could not find information. Try something else.');
|
||||
return this.error(message.channel, 'Please try again later, something unexpected happened.');
|
||||
}
|
||||
|
||||
return message.channel.createMessage(res.data);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import axios from 'axios';
|
||||
import { inspect } from 'util';
|
||||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Eval extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'eval';
|
||||
this.description = 'Evaluates native JS code';
|
||||
this.aliases = ['e'];
|
||||
this.permissions = 7;
|
||||
this.enabled = true;
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
const evalMessage = message.content.slice(this.client.config.prefix.length).trim().split(' ').slice(1);
|
||||
let evalString = evalMessage.join(' ').trim();
|
||||
let evaled: any;
|
||||
let depth = 0;
|
||||
|
||||
if (args[0] && args[0].startsWith('-d')) {
|
||||
depth = Number(args[0].replace('-d', ''));
|
||||
if (!depth || depth < 0) depth = 0;
|
||||
const index = evalMessage.findIndex((v) => v.startsWith('-d')) + 1;
|
||||
evalString = evalMessage.slice(index).join(' ').trim();
|
||||
}
|
||||
if (args[0] === '-a') {
|
||||
const index = evalMessage.findIndex((v) => v === '-a') + 1;
|
||||
evalString = `(async () => { ${evalMessage.slice(index).join(' ').trim()} })()`;
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-eval
|
||||
evaled = await eval(evalString);
|
||||
if (typeof evaled !== 'string') {
|
||||
evaled = inspect(evaled, { depth });
|
||||
}
|
||||
if (evaled === undefined) {
|
||||
evaled = 'undefined';
|
||||
}
|
||||
} catch (error) {
|
||||
evaled = error.stack;
|
||||
}
|
||||
|
||||
evaled = evaled.replace(new RegExp(this.client.config.token, 'gi'), 'juul');
|
||||
// evaled = evaled.replace(new RegExp(this.client.config.emailPass, 'gi'), 'juul');
|
||||
// evaled = evaled.replace(new RegExp(this.client.config.cloudflare, 'gi'), 'juul');
|
||||
|
||||
|
||||
const display = this.client.util.splitString(evaled, 1975);
|
||||
if (display[5]) {
|
||||
try {
|
||||
const { data } = await axios.post('https://snippets.cloud.libraryofcode.org/documents', display.join(''));
|
||||
return this.success(message.channel, `Your evaluation evaled can be found on https://snippets.cloud.libraryofcode.org/${data.key}`);
|
||||
} catch (error) {
|
||||
return this.error(message.channel, `${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
return display.forEach((m) => message.channel.createMessage(`\`\`\`js\n${m}\n\`\`\``));
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/* eslint-disable prefer-destructuring */
|
||||
import { Activity, Member, Message } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
enum ActivityType {
|
||||
PLAYING,
|
||||
STREAMING,
|
||||
LISTENING,
|
||||
WATCHING,
|
||||
CUSTOM_STATUS
|
||||
}
|
||||
|
||||
export default class Game extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'game';
|
||||
this.description = 'Displays information about the member\'s game.';
|
||||
this.usage = 'game [member]';
|
||||
this.permissions = 0;
|
||||
this.aliases = ['activity'];
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
let member: Member;
|
||||
if (!args[0]) member = message.member;
|
||||
else {
|
||||
member = this.client.util.resolveMember(args.join(' '), this.mainGuild);
|
||||
if (!member) {
|
||||
return this.error(message.channel, 'Member not found.');
|
||||
}
|
||||
}
|
||||
if (!member.activities || member.activities.length <= 0) return this.error(message.channel, 'Cannot find a game for this member.');
|
||||
const embed = new RichEmbed();
|
||||
let mainStatus: Activity;
|
||||
if (member.activities[0]?.type === ActivityType.CUSTOM_STATUS) {
|
||||
mainStatus = member.activities[1];
|
||||
embed.setDescription(`*${member.activities[0].state}*`);
|
||||
} else {
|
||||
mainStatus = member.activities[0];
|
||||
}
|
||||
embed.setAuthor(member.user.username, member.user.avatarURL);
|
||||
if (mainStatus?.type === ActivityType.LISTENING) {
|
||||
embed.setTitle('Spotify');
|
||||
embed.setColor('#1ed760');
|
||||
embed.addField('Song', mainStatus.details, true);
|
||||
embed.addField('Artist', mainStatus.state, true);
|
||||
embed.addField('Album', mainStatus.assets.large_text);
|
||||
embed.addField('Start', `${new Date(mainStatus.timestamps.start).toLocaleTimeString('en-us')} ET`, true);
|
||||
embed.addField('End', `${new Date(mainStatus.timestamps.end).toLocaleTimeString('en-us')} ET`, true);
|
||||
embed.setThumbnail(`https://i.scdn.co/image/${mainStatus.assets.large_image.split(':')[1]}`);
|
||||
embed.setFooter(`Listening to Spotify | ${this.client.user.username}`, 'https://media.discordapp.net/attachments/358674161566220288/496894273304920064/2000px-Spotify_logo_without_text.png');
|
||||
embed.setTimestamp();
|
||||
} else {
|
||||
return this.error(message.channel, 'Only Spotify games are supported at this time.');
|
||||
}
|
||||
return message.channel.createMessage({ embed });
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
import { createPaginationEmbed } from 'eris-pagination';
|
||||
import { Message } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class Help extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'help';
|
||||
this.description = 'Information about commands.';
|
||||
this.usage = 'help [command]';
|
||||
this.permissions = 0;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (args.length > 0) {
|
||||
const resolved = await this.client.util.resolveCommand(args, message);
|
||||
if (!resolved) return this.error(message.channel, 'The command you provided doesn\'t exist.');
|
||||
const { cmd } = resolved;
|
||||
const embed = new RichEmbed();
|
||||
const subcommands = cmd.subcommands.size ? `\n**Subcommands:** ${cmd.subcommands.map((s) => `${cmd.name} ${s.name}`).join(', ')}` : '';
|
||||
embed.setTitle(`${this.client.config.prefix}${cmd.name}`);
|
||||
embed.addField('Description', cmd.description ?? '-');
|
||||
embed.addField('Usage', cmd.usage ?? '-');
|
||||
if (subcommands) embed.addField('Sub-commands', subcommands);
|
||||
if (cmd.aliases.length > 0) {
|
||||
embed.addField('Aliases', cmd.aliases.map((alias) => `${this.client.config.prefix}${alias}`).join(', '));
|
||||
}
|
||||
let description: string = '';
|
||||
if (!cmd.enabled) {
|
||||
description += 'This command is disabled.';
|
||||
}
|
||||
if (cmd.guildOnly) {
|
||||
description += 'This command can only be ran in a guild.';
|
||||
}
|
||||
embed.setDescription(description);
|
||||
switch (cmd.permissions) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
embed.addField('Permissions', 'Associates+');
|
||||
break;
|
||||
case 2:
|
||||
embed.addField('Permissions', 'Core Team+');
|
||||
break;
|
||||
case 3:
|
||||
embed.addField('Permissions', 'Moderators, Supervisor, & Board of Directors');
|
||||
break;
|
||||
case 4:
|
||||
embed.addField('Permissions', 'Technicians, Supervisor, & Board of Directors');
|
||||
break;
|
||||
case 5:
|
||||
embed.addField('Permissions', 'Moderators, Technicians, Supervisor, & Board of Directors');
|
||||
break;
|
||||
case 6:
|
||||
embed.addField('Permissions', 'Supervisor+');
|
||||
break;
|
||||
case 7:
|
||||
embed.addField('Permissions', 'Board of Directors');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
return message.channel.createMessage({ embed });
|
||||
}
|
||||
const cmdList: Command[] = [];
|
||||
this.client.commands.forEach((c) => {
|
||||
if (c.permissions !== 0 && c.guildOnly) {
|
||||
const check = c.checkCustomPermissions(message.member, c.permissions);
|
||||
if (!check) return;
|
||||
}
|
||||
cmdList.push(c);
|
||||
});
|
||||
const commands = this.client.commands.map((c) => {
|
||||
const aliases = c.aliases.map((alias) => `${this.client.config.prefix}${alias}`).join(', ');
|
||||
let perm: string;
|
||||
switch (c.permissions) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
perm = 'Associates+';
|
||||
break;
|
||||
case 2:
|
||||
perm = 'Core Team+';
|
||||
break;
|
||||
case 3:
|
||||
perm = 'Moderators, Supervisor, & Board of Directors';
|
||||
break;
|
||||
case 4:
|
||||
perm = 'Technicians, Supervisor, & Board of Directors';
|
||||
break;
|
||||
case 5:
|
||||
perm = 'Moderators, Technicians, Supervisor, & Board of Directors';
|
||||
break;
|
||||
case 6:
|
||||
perm = 'Supervisor+';
|
||||
break;
|
||||
case 7:
|
||||
perm = 'Board of Directors';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return { name: `${this.client.config.prefix}${c.name}`, value: `**Description:** ${c.description}\n**Aliases:** ${aliases}\n**Usage:** ${c.usage}\n**Permissions:** ${perm ?? ''}`, inline: false };
|
||||
});
|
||||
const splitCommands = this.client.util.splitFields(commands);
|
||||
const cmdPages: RichEmbed[] = [];
|
||||
splitCommands.forEach((splitCmd) => {
|
||||
const embed = new RichEmbed();
|
||||
embed.setTimestamp(); embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setDescription(`Command list for ${this.client.user.username}`);
|
||||
splitCmd.forEach((c) => embed.addField(c.name, c.value, c.inline));
|
||||
return cmdPages.push(embed);
|
||||
});
|
||||
if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] });
|
||||
return createPaginationEmbed(message, cmdPages);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
export { default as additem } from './additem';
|
||||
export { default as addmerchant } from './addmerchant';
|
||||
export { default as addnote } from './addnote';
|
||||
export { default as addrank } from './addrank';
|
||||
export { default as addredirect } from './addredirect';
|
||||
export { default as apply } from './apply';
|
||||
export { default as ban } from './ban';
|
||||
export { default as billing } from './billing';
|
||||
export { default as callback } from './callback';
|
||||
export { default as delitem } from './delitem';
|
||||
export { default as delmerchant } from './delmerchant';
|
||||
export { default as delnote } from './delnote';
|
||||
export { default as delrank } from './delrank';
|
||||
export { default as delredirect } from './delredirect';
|
||||
export { default as djs } from './djs';
|
||||
export { default as eris } from './eris';
|
||||
export { default as eval } from './eval';
|
||||
export { default as game } from './game';
|
||||
export { default as help } from './help';
|
||||
export { default as info } from './info';
|
||||
export { default as intercom } from './intercom';
|
||||
export { default as kick } from './kick';
|
||||
export { default as listredirects } from './listredirects';
|
||||
export { default as market } from './market';
|
||||
export { default as members } from './members';
|
||||
export { default as mute } from './mute';
|
||||
export { default as notes } from './notes';
|
||||
export { default as npm } from './npm';
|
||||
export { default as offer } from './offer';
|
||||
export { default as page } from './page';
|
||||
export { default as ping } from './ping';
|
||||
export { default as profile } from './profile';
|
||||
export { default as pulldata } from './pulldata';
|
||||
export { default as rank } from './rank';
|
||||
export { default as role } from './role';
|
||||
export { default as roleinfo } from './roleinfo';
|
||||
export { default as score } from './score';
|
||||
export { default as sip } from './sip';
|
||||
export { default as site } from './site';
|
||||
export { default as slowmode } from './slowmode';
|
||||
export { default as stats } from './stats';
|
||||
export { default as storemessages } from './storemessages';
|
||||
export { default as sysinfo } from './sysinfo';
|
||||
export { default as train } from './train';
|
||||
export { default as tts } from './tts';
|
||||
export { default as unban } from './unban';
|
||||
export { default as unmute } from './unmute';
|
||||
export { default as whois } from './whois';
|
|
@ -0,0 +1,37 @@
|
|||
import { Message } from 'eris';
|
||||
import { totalmem } from 'os';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
import { version as tsVersion } from '../../node_modules/typescript/package.json';
|
||||
|
||||
export default class Info extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'info';
|
||||
this.description = 'Information about this application.';
|
||||
this.usage = 'info';
|
||||
this.permissions = 0;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message) {
|
||||
try {
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Information');
|
||||
embed.setThumbnail(this.client.user.avatarURL);
|
||||
embed.setDescription(`*See \`${this.client.config.prefix}sysinfo\` for more information on libraries used by this application.*`);
|
||||
embed.addField('Version', 'Rolling Release', true);
|
||||
embed.addField('Language(s)', '<:TypeScript:703451285789343774> TypeScript', true);
|
||||
embed.addField('Runtime', `Node (${process.version})`, true);
|
||||
embed.addField('Compilers/Transpilers', `TypeScript [tsc] (${tsVersion})`, true);
|
||||
embed.addField('Memory Usage', `${Math.round(process.memoryUsage().rss / 1024 / 1024)} MB / ${Math.round(totalmem() / 1024 / 1024 / 1024)} GB`, true);
|
||||
embed.addField('Repository', 'https://loc.sh/crgit | Licensed under GNU Affero General Public License V3', true);
|
||||
embed.addField('Branch', await this.client.util.exec('git rev-parse --abbrev-ref HEAD'), true);
|
||||
embed.addField('Commit', `[${await this.client.util.exec('git rev-parse --short HEAD')}](${await this.client.util.exec('git rev-parse HEAD')})`, true);
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
message.channel.createMessage({ embed });
|
||||
} catch (err) {
|
||||
this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
import { Misc as MiscPBXActions } from '../pbx';
|
||||
|
||||
export default class Intercom extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'intercom';
|
||||
this.description = 'Will synthesize inputted text to a recording and dial an intercom to the extension specified, then play the recording.';
|
||||
this.usage = `${this.client.config.prefix}intercom <extension> <text>`;
|
||||
this.permissions = 1;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const loading = await this.loading(message.channel, 'Synthesizing text...');
|
||||
|
||||
const recordingLocation = await MiscPBXActions.TTS(this.client.util.pbx, `Hello, this is the Library of Code Private Branch Exchange dialing you at the request of ${message.author.username} to deliver you a message. Playing message: ${args.slice(1).join(' ')}`, 'MALE');
|
||||
await loading.edit(`***${this.client.util.emojis.LOADING} Preparing to dial...***`);
|
||||
this.client.util.pbx.ami.action({
|
||||
action: 'originate',
|
||||
channel: `PJSIP/${args[0]}`,
|
||||
exten: args[0],
|
||||
context: 'from-internal',
|
||||
CallerID: `TTS INTC FRM ${message.author.username} <000>`,
|
||||
application: 'PlayBack',
|
||||
priority: '1',
|
||||
data: `beep&${recordingLocation.split(':')[1]}`,
|
||||
variable: {
|
||||
'PJSIP_HEADER(add,Call-Info)': '<uri>;answer-after=0',
|
||||
'PJSIP_HEADER(add,Alert-Info)': 'Ring Answer',
|
||||
},
|
||||
}, async (err: Error) => {
|
||||
if (err) return loading.edit(`***${this.client.util.emojis.ERROR} Failed to dial extension.***`);
|
||||
return loading.edit(`***${this.client.util.emojis.SUCCESS} Successfully queued intercom message to EXT \`${args[0]}\`.***`);
|
||||
});
|
||||
return undefined;
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import { Member, Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Kick extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'kick';
|
||||
this.description = 'Kicks a member from the guild.';
|
||||
this.usage = 'kick <member> [reason]';
|
||||
this.permissions = 3;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
let user: Member = this.client.util.resolveMember(args[0], this.mainGuild);
|
||||
if (!user) {
|
||||
try {
|
||||
user = await this.client.getRESTGuildMember(this.mainGuild.id, args[0]);
|
||||
} catch {
|
||||
return this.error(message.channel, 'Cannot find user.');
|
||||
}
|
||||
}
|
||||
if (user && !this.client.util.moderation.checkPermissions(user, message.member)) return this.error(message.channel, 'Permission Denied.');
|
||||
message.delete();
|
||||
|
||||
const reason: string = args.slice(1).join(' ');
|
||||
if (reason.length > 512) return this.error(message.channel, 'Kick reasons cannot be longer than 512 characters.');
|
||||
await this.client.util.moderation.kick(user, message.member, reason);
|
||||
return this.success(message.channel, `${user.username}#${user.discriminator} has been kicked.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import { Message } from 'eris';
|
||||
import { createPaginationEmbed } from 'eris-pagination';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class DelRedirect extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'listredirects';
|
||||
this.description = 'Delete a redirect link for \'loc.sh\'';
|
||||
this.usage = 'listredirects [key || redirect to]';
|
||||
this.aliases = ['getredirect', 'lsredirects', 'listlinks', 'lsr', 'gr'];
|
||||
this.permissions = 6;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (args[0]) {
|
||||
const redirects = await this.client.db.Redirect.find({ $or: [{ key: args[0].toLowerCase() }, { to: args[0].toLowerCase() }] });
|
||||
if (redirects.length <= 0) return this.error(message.channel, 'Could not find an entry matching that query.');
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Redirect Information');
|
||||
for (const redirect of redirects) {
|
||||
embed.addField(`${redirect.key} | visited ${redirect.visitedCount} times`, redirect.to);
|
||||
}
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
return message.channel.createMessage({ embed });
|
||||
}
|
||||
const redirects = await this.client.db.Redirect.find();
|
||||
if (!redirects) return this.error(message.channel, 'No redirect links found.');
|
||||
const redirectArray: [{ name: string, value: string }?] = [];
|
||||
for (const redirect of redirects) {
|
||||
redirectArray.push({ name: `${redirect.key} | visited ${redirect.visitedCount} times`, value: redirect.to });
|
||||
}
|
||||
const splitRedirects = this.client.util.splitFields(redirectArray);
|
||||
const cmdPages: RichEmbed[] = [];
|
||||
splitRedirects.forEach((split) => {
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Redirect Information');
|
||||
embed.setTimestamp();
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
split.forEach((c) => embed.addField(c.name, c.value));
|
||||
return cmdPages.push(embed);
|
||||
});
|
||||
if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] });
|
||||
return createPaginationEmbed(message, cmdPages);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import { Message } from 'eris';
|
||||
import stockInfo from 'stock-info';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class Market extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'market';
|
||||
this.description = 'Fetches information from a ticker for a stock or fund.';
|
||||
this.usage = `${this.client.config.prefix}market <ticker>`;
|
||||
this.permissions = 0;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
let stock: stockInfo.Stock;
|
||||
try {
|
||||
stock = await stockInfo.getSingleStockInfo(args[0]);
|
||||
} catch (err) {
|
||||
return this.error(message.channel, `Unable to fetch information for that ticker. | ${err}`);
|
||||
}
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle(stock.longName ?? stock.symbol);
|
||||
let type: string;
|
||||
switch (stock.quoteType) {
|
||||
case 'EQUITY':
|
||||
type = 'Common/Preferred Stock';
|
||||
break;
|
||||
case 'ETF':
|
||||
type = 'Exchange Traded Fund (ETF)';
|
||||
break;
|
||||
case 'MUTUALFUND':
|
||||
type = 'Mutual Fund';
|
||||
break;
|
||||
default:
|
||||
type = 'N/A or Unknown';
|
||||
break;
|
||||
}
|
||||
embed.addField('Type', type, true);
|
||||
embed.addField('Market Cap', `${stock.marketCap ? this.client.util.hrn(stock.marketCap, undefined, undefined) : 'N/A'}`, true);
|
||||
embed.addField('Day Quote Price', stock.regularMarketPrice ? `$${Number(stock.regularMarketPrice).toFixed(2)}` : 'N/A', true);
|
||||
embed.addField('Day G/L', stock.regularMarketChange ? `$${Number(stock.regularMarketChange.toFixed(2))}` : 'N/A', true);
|
||||
embed.addField('Day Range', stock.regularMarketDayRange ?? 'N/A', true);
|
||||
embed.addField('Bid/Ask', `Bid: $${stock.bid?.toFixed(2) ?? 'N/A'} | Ask: $${stock.ask?.toFixed(2) ?? 'N/A'}`, true);
|
||||
embed.addField('Forward P/E (Price/Earnings)', `${stock.forwardPE?.toFixed(2) ?? 'N/A'}`, true);
|
||||
embed.addField('Forward EPS (Earnings Per Share)', `${stock.epsForward?.toFixed(2) ?? 'N/A'}`, true);
|
||||
embed.addField('Exchange', `${stock.fullExchangeName?.toUpperCase() ?? 'N/A'}`, true);
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
return message.channel.createMessage({ embed });
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import { Message } from 'eris';
|
||||
import { createPaginationEmbed } from 'eris-pagination';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class Members extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'members';
|
||||
this.description = 'Gets a list of members in the server or members in a specific role.';
|
||||
this.usage = `${this.client.config.prefix}members [role name]`;
|
||||
this.permissions = 0;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
await this.mainGuild.fetchAllMembers();
|
||||
if (!args[0]) {
|
||||
const embed = new RichEmbed();
|
||||
const membersOnline = this.mainGuild.members.filter((member) => member.status === 'online');
|
||||
const membersIdle = this.mainGuild.members.filter((member) => member.status === 'idle');
|
||||
const membersDnd = this.mainGuild.members.filter((member) => member.status === 'dnd');
|
||||
const membersOffline = this.mainGuild.members.filter((member) => member.status === 'offline' || member.status === undefined);
|
||||
const membersBots = this.mainGuild.members.filter((member) => member.user.bot === true);
|
||||
const membersHuman = this.mainGuild.members.filter((member) => member.user.bot === false);
|
||||
|
||||
embed.setTitle('Members');
|
||||
embed.setDescription(`**Total:** ${this.mainGuild.members.size}\n**Humans:** ${membersHuman.length}\n**Bots:** ${membersBots.length}\n\n**<:online:732025023547834369> Online:** ${membersOnline.length}\n**<:idle:732025087896715344> Idle:** ${membersIdle.length}\n**<:dnd:732024861853089933> Do Not Disturb:** ${membersDnd.length}\n**<:offline:732024920518688849> Offline:** ${membersOffline.length}`);
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
|
||||
return message.channel.createMessage({ embed });
|
||||
}
|
||||
|
||||
const role = this.client.util.resolveRole(args.join(' '), this.mainGuild);
|
||||
if (!role) return this.error(message.channel, 'The role you specified doesn\'t exist.');
|
||||
const statusArray: string[] = [];
|
||||
const membersOnline: string[] = [];
|
||||
const membersIdle: string[] = [];
|
||||
const membersDnd: string[] = [];
|
||||
const membersOffline: string[] = [];
|
||||
for (const member of this.mainGuild.members.filter((m) => m.roles.includes(role.id)).sort((a, b) => a.username.localeCompare(b.username))) {
|
||||
switch (member.status) {
|
||||
case 'online':
|
||||
membersOnline.push(`<:online:732025023547834369> ${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`);
|
||||
break;
|
||||
case 'idle':
|
||||
membersIdle.push(`<:idle:732025087896715344> ${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`);
|
||||
break;
|
||||
case 'dnd':
|
||||
membersDnd.push(`<:dnd:732024861853089933> ${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`);
|
||||
break;
|
||||
case 'offline':
|
||||
membersOffline.push(`<:offline:732024920518688849> ${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`);
|
||||
break;
|
||||
case undefined:
|
||||
membersOffline.push(`<:offline:732024920518688849> ${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (membersOnline.length > 0) statusArray.push(membersOnline.join('\n'));
|
||||
if (membersIdle.length > 0) statusArray.push(membersIdle.join('\n'));
|
||||
if (membersDnd.length > 0) statusArray.push(membersDnd.join('\n'));
|
||||
if (membersOffline.length > 0) statusArray.push(membersOffline.join('\n'));
|
||||
const statusSplit = this.client.util.splitString(statusArray.join('\n'), 2000);
|
||||
const cmdPages: RichEmbed[] = [];
|
||||
statusSplit.forEach((split) => {
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle(`Members in ${role.name}`);
|
||||
embed.setDescription(`Members in Role: ${membersOnline.length + membersIdle.length + membersDnd.length + membersOffline.length}\n\n${split}`);
|
||||
embed.setColor(role.color);
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
return cmdPages.push(embed);
|
||||
});
|
||||
if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] });
|
||||
return createPaginationEmbed(message, cmdPages);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import moment, { unitOfTime } from 'moment';
|
||||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Mute extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'mute';
|
||||
this.description = 'Mutes a member.';
|
||||
this.usage = 'mute <member> [time] [reason]';
|
||||
this.permissions = 2;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const member = this.client.util.resolveMember(args[0], this.mainGuild);
|
||||
if (!member) return this.error(message.channel, 'Cannot find user.');
|
||||
|
||||
try {
|
||||
const res1 = await this.client.db.local.muted.get<boolean>(`muted-${member.id}`);
|
||||
if (res1 || this.mainGuild.members.get(member.id).roles.includes('478373942638149643')) return this.error(message.channel, 'This user is already muted.');
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
if (member && !this.client.util.moderation.checkPermissions(member, message.member)) return this.error(message.channel, 'Permission Denied.');
|
||||
message.delete();
|
||||
|
||||
let momentMilliseconds: number;
|
||||
let reason: string;
|
||||
if (args.length > 1) {
|
||||
const lockLength = args[1].match(/[a-z]+|[^a-z]+/gi);
|
||||
const length = Number(lockLength[0]);
|
||||
const unit = lockLength[1] as unitOfTime.Base;
|
||||
momentMilliseconds = moment.duration(length, unit).asMilliseconds();
|
||||
reason = momentMilliseconds ? args.slice(2).join(' ') : args.slice(1).join(' ');
|
||||
if (reason.length > 512) return this.error(message.channel, 'Mute reasons cannot be longer than 512 characters.');
|
||||
}
|
||||
await this.client.util.moderation.mute(member.user, message.member, momentMilliseconds, reason);
|
||||
return this.success(message.channel, `${member.user.username}#${member.user.discriminator} has been muted.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
import { Message, Member, User } from 'eris';
|
||||
import { createPaginationEmbed } from 'eris-pagination';
|
||||
import { Command, Client, RichEmbed } from '../class';
|
||||
|
||||
export default class Notes extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'notes';
|
||||
this.description = 'Pulls up notes for a member, with an optional categorical filter.';
|
||||
this.usage = `${this.client.config.prefix}notes <member> [filter: comm | cs | edu]`;
|
||||
this.permissions = 1;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
let member: Member | User = this.client.util.resolveMember(args[0], this.mainGuild);
|
||||
if (!member) {
|
||||
try {
|
||||
member = await this.client.getRESTUser(args[0]);
|
||||
} catch {
|
||||
member = undefined;
|
||||
}
|
||||
}
|
||||
if (!member) return this.error(message.channel, 'User specified could not be found.');
|
||||
|
||||
const notes = await this.client.db.Note.find({ userID: member.id });
|
||||
if (!notes || notes?.length < 1) return this.error(message.channel, 'No notes exist for this user.');
|
||||
|
||||
const noteArray: [{ name: string, value: string, inline: boolean }?] = [];
|
||||
if (args[1] === 'comm' || args[1] === 'cs' || args[1] === 'edu') {
|
||||
switch (args[1]) {
|
||||
case 'comm':
|
||||
for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime()).filter((r) => r.category === 'comm')) {
|
||||
noteArray.push({
|
||||
name: `${note._id}${note.category === '' ? '' : `, ${note.category.toUpperCase()}`} | ${note.date.toLocaleString('en-us')} ET | Staff: ${this.client.users.get(note.staffID) ? `${this.client.users.get(note.staffID).username}#${this.client.users.get(note.staffID).discriminator}` : 'N/A'}`,
|
||||
value: note.text,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'cs':
|
||||
for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime()).filter((r) => r.category === 'cs')) {
|
||||
noteArray.push({
|
||||
name: `${note._id}${note.category === '' ? '' : `, ${note.category.toUpperCase()}`} | ${note.date.toLocaleString('en-us')} ET | Staff: ${this.client.users.get(note.staffID) ? `${this.client.users.get(note.staffID).username}#${this.client.users.get(note.staffID).discriminator}` : 'N/A'}`,
|
||||
value: note.text,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'edu':
|
||||
for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime()).filter((r) => r.category === 'edu')) {
|
||||
noteArray.push({
|
||||
name: `${note._id}${note.category === '' ? '' : `, ${note.category.toUpperCase()}`}} | ${note.date.toLocaleString('en-us')} ET | Staff: Staff: ${this.client.users.get(note.staffID) ? `${this.client.users.get(note.staffID).username}#${this.client.users.get(note.staffID).discriminator}` : 'N/A'}`,
|
||||
value: note.text,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime())) {
|
||||
noteArray.push({
|
||||
name: `${note._id}${note.category === '' ? '' : `, ${note.category}`} | ${note.date.toLocaleString('en-us')} ET | Staff: ${this.client.users.get(note.staffID) ? `${this.client.users.get(note.staffID).username}#${this.client.users.get(note.staffID).discriminator}` : 'N/A'}`,
|
||||
value: note.text,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
const noteSplit = this.client.util.splitFields(noteArray);
|
||||
const cmdPages: RichEmbed[] = [];
|
||||
noteSplit.forEach((split) => {
|
||||
const embed = new RichEmbed();
|
||||
embed.setColor('#0000FF');
|
||||
embed.setAuthor(`${member.username}#${member.discriminator}`, member.avatarURL);
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
split.forEach((c) => embed.addField(c.name, c.value, c.inline));
|
||||
return cmdPages.push(embed);
|
||||
});
|
||||
if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] });
|
||||
return createPaginationEmbed(message, cmdPages);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import { Message } from 'eris';
|
||||
import axios from 'axios';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class NPM extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'npm';
|
||||
this.description = 'Get information about npm modules.';
|
||||
this.usage = 'npm <module name>';
|
||||
this.permissions = 0;
|
||||
this.guildOnly = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
|
||||
const res = await axios.get(`https://registry.npmjs.com/${args[0]}`, { validateStatus: (_) => true });
|
||||
|
||||
if (res.status === 404) return this.error(message.channel, 'Could not find the library, try something else.');
|
||||
|
||||
const { data } = res;
|
||||
|
||||
const bugs: string = data.bugs?.url || '';
|
||||
const description: string = data.description || 'None';
|
||||
const version: string = data['dist-tags']?.latest || 'Unknown';
|
||||
const homepage: string = data.homepage || '';
|
||||
let license: string = 'None';
|
||||
if (typeof data.license === 'object') {
|
||||
license = data.license.type;
|
||||
} else if (typeof data.license === 'string') {
|
||||
license = data.license;
|
||||
}
|
||||
let dependencies: string = 'None';
|
||||
if (version !== 'Unknown' && data.versions[version].dependencies !== undefined && Object.keys(data.versions[version].dependencies).length > 0) {
|
||||
dependencies = Object.keys(data.versions[version].dependencies).join(', ');
|
||||
if (dependencies.length > 1024) dependencies = `${dependencies.substr(0, 1021)}...`;
|
||||
}
|
||||
const name: string = data.name || 'None';
|
||||
const repository: string = bugs.replace('/issues', '') || '';
|
||||
const creation: string = data?.time.created ? new Date(data.time.created).toLocaleString('en') : 'None';
|
||||
const modification: string = data?.time.modified ? new Date(data.time.modified).toLocaleString('en') : 'None';
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.setColor(0xCC3534);
|
||||
embed.setTimestamp();
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setAuthor('NPM', 'https://i.imgur.com/ErKf5Y0.png', 'https://www.npmjs.com/');
|
||||
embed.setDescription(`[NPM](https://www.npmjs.com/package/${args[0]}) | [Homepage](${homepage}) | [Repository](${repository}) | [Bugs](${bugs})`);
|
||||
embed.addField('Name', name, true);
|
||||
embed.addField('Latest version', version, true);
|
||||
embed.addField('License', license, true);
|
||||
embed.addField('Description', description, false);
|
||||
embed.addField('Dependencies', dependencies, false);
|
||||
embed.addField('Creation Date', creation, true);
|
||||
embed.addField('Modification Date', modification, true);
|
||||
|
||||
return message.channel.createMessage({ embed });
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/* eslint-disable default-case */
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Offer extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'offer';
|
||||
this.description = 'Pre-qualifies a member for an offer. Will run a hard-pull automatically on acceptance.';
|
||||
this.usage = `${this.client.config.prefix}offer <member> <name of offer>:<department/service for hard pull>`;
|
||||
this.permissions = 4;
|
||||
this.aliases = ['qualify'];
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const member = this.client.util.resolveMember(args[0], this.mainGuild);
|
||||
if (!member) return this.error(message.channel, 'Could not find member.');
|
||||
const score = await this.client.db.Score.findOne({ userID: member.user.id }).lean().exec();
|
||||
if (!score) return this.error(message.channel, 'Could not find score report for this user.');
|
||||
|
||||
if (score.locked) return this.error(message.channel, 'This user\'s score report is locked.');
|
||||
|
||||
const name = args.slice(1).join(' ').split(':')[0];
|
||||
const dept = args.slice(1).join(' ').split(':')[1];
|
||||
if (!name || !dept) return this.error(message.channel, 'Invalid arguments.');
|
||||
|
||||
const token = jwt.sign({
|
||||
userID: member.user.id,
|
||||
staffID: message.author.id,
|
||||
channelID: message.channel.id,
|
||||
messageID: message.id,
|
||||
pin: score.pin.join('-'),
|
||||
name,
|
||||
department: dept,
|
||||
date: new Date(),
|
||||
}, this.client.config.internalKey, { expiresIn: '12h' });
|
||||
this.client.getDMChannel(member.user.id).then((chan) => {
|
||||
chan.createMessage(`__**Offer Pre-Qualified**__\nYou have been pre-approved for an offer! If you wish to accept this offer, please enter the offer code at https://report.libraryofcode.org/. Do not share this code with anyone else. This offer automatically expires in 12 hours. Your report will be Hard Inquiried immediately after accepting this offer.\n\n**Department:** ${dept.toUpperCase()}\n**Offer for:** ${name}\n\n\`${token}\``);
|
||||
}).catch(() => this.error(message.channel, 'Could not DM member.'));
|
||||
return this.success(message.channel, `Offer sent.\n\n\`${token}\``);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
/* eslint-disable no-case-declarations */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { promises as fs } from 'fs';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { Message, TextableChannel } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class Page extends Command {
|
||||
public local: { emergencyNumbers: string[], departmentNumbers: string[], validPagerCodes: string[], codeDict: Map<string, string>, };
|
||||
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'page';
|
||||
this.description = 'Pages the specified emergency number, department number, or individual number with the specified pager code.';
|
||||
this.usage = `${this.client.config.prefix}page <pager number> <pager code> [optional message]\n${this.client.config.prefix}page settings <type: email | phone> <options: [email: on/off | phone: on/off]>`;
|
||||
this.aliases = ['p'];
|
||||
this.permissions = 1;
|
||||
this.enabled = true;
|
||||
this.guildOnly = true;
|
||||
this.local = {
|
||||
emergencyNumbers: ['#0', '#1', '#2', '#3'],
|
||||
departmentNumbers: ['00', '01', '10', '20', '21', '22'],
|
||||
validPagerCodes: ['911', '811', '210', '265', '411', '419', '555', '556', '557', '558'],
|
||||
codeDict: new Map(),
|
||||
};
|
||||
this.init();
|
||||
}
|
||||
|
||||
public init() {
|
||||
this.local.codeDict.set('911', 'Sender is requesting EMERGENCY assistance.');
|
||||
this.local.codeDict.set('811', 'Sender is requesting immediate/ASAP assistance.');
|
||||
this.local.codeDict.set('210', 'Sender is informing you they acknowledged your request, usually sent in response to OK the initial page. (10-4)');
|
||||
this.local.codeDict.set('265', 'Sender is requesting that you check your email.');
|
||||
this.local.codeDict.set('411', 'Sender is requesting information/counsel from you.');
|
||||
this.local.codeDict.set('419', 'Sender didn\'t recognize your request.');
|
||||
this.local.codeDict.set('555', 'Sender is requesting that you contact them.');
|
||||
this.local.codeDict.set('556', 'Sender is requesting that you contact them via DMs.');
|
||||
this.local.codeDict.set('557', 'Sender is requesting that you contact them via PBX/their extension.');
|
||||
this.local.codeDict.set('558', 'Sender is requesting if they are able to call you via PBX. If so, please send the sender back a 210 page.');
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) {
|
||||
this.client.commands.get('help').run(message, [this.name]);
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Special Emergency/Department Numbers & Pager Codes');
|
||||
embed.addField('Special Emergency Numbers', '`#0` | Broadcast - all Staff/Associates\n`#1` | Authoritative Broadcast - all Directors, Supervisors, Technicians, and Moderators\n`#2` | Systems Administrators/Technicians Broadcast - Matthew, Bsian, NightRaven, and all Technicians\n`#3` | Community/Moderation Team Broadcast - all Directors, Supervisors, Moderators, and Core Team');
|
||||
embed.addField('Department Numbers', '`00` | Board of Directors\n`01` | Supervisors\n`10` | Technicians\n`20` | Moderators\n`21` | Core Team\n`22` | Associates');
|
||||
embed.addField('Pager Codes', '"Pager" term in this field refers to the Staff member that initially paged. This is a list of valid codes you can send via a page.\n\n`911` - Pager is requesting EMERGENCY assistance\n`811` - Pager is requesting immediate/ASAP assistance\n`210` - Pager is informing you they acknowledged your request, usually sent in response to OK the initial page.\n`265` - Pager is requesting that you check your email\n`411` - Pager is requesting information/counsel from you\n`419` - Pager didn\'t recognize your request\n`555` - Pager is requesting that you contact them\n`556` - Pager is requesting that you contact them via DMs\n`557` - Pager is requesting that you contact them via PBX/their extension.\n`558` - Pager is requesting if they are able to call you via PBX. If so, please send the pager back a 210 page.');
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
return message.channel.createMessage({ embed });
|
||||
}
|
||||
if (args[0] === 'settings') {
|
||||
const pager = await this.client.db.PagerNumber.findOne({ individualAssignID: message.author.id });
|
||||
if (!pager) return this.error(message.channel, 'You do not have a Pager Number.');
|
||||
switch (args[1]) {
|
||||
case 'email':
|
||||
if (args[2] === 'off') {
|
||||
if (pager.receiveEmail === false) return this.error(message.channel, 'You are already set to not receive email notifications.');
|
||||
await pager.updateOne({ $set: { receiveEmail: false } });
|
||||
return this.success(message.channel, 'You will no longer receive notifications by email for pages.');
|
||||
}
|
||||
if (args[2] === 'on') {
|
||||
if (pager.receiveEmail === true) return this.error(message.channel, 'You are already set to receive email notifications.');
|
||||
await pager.updateOne({ $set: { receiveEmail: true } });
|
||||
return this.success(message.channel, 'You will now receive notifications by email for pages.');
|
||||
}
|
||||
break;
|
||||
case 'phone':
|
||||
if (args[2] === 'off') {
|
||||
if (pager.receivePhone === false) return this.error(message.channel, 'You are already set to not receive PBX calls.');
|
||||
await pager.updateOne({ $set: { receivePhone: false } });
|
||||
return this.success(message.channel, 'You will no longer receive PBX calls for pages.');
|
||||
}
|
||||
if (args[2] === 'on') {
|
||||
if (pager.receivePhone === true) return this.error(message.channel, 'You are already set to receive PBX calls.');
|
||||
await pager.updateOne({ $set: { receivePhone: true } });
|
||||
return this.success(message.channel, 'You will now receive PBX calls for pages.');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.error(message.channel, 'Invalid response provided.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
message.delete();
|
||||
const loading = await this.loading(message.channel, 'Paging...');
|
||||
const sender = await this.client.db.PagerNumber.findOne({ individualAssignID: message.author.id });
|
||||
if (!sender) return this.error(message.channel, 'You do not have a Pager Number.');
|
||||
const page = await this.page(args[0], sender.num, args[1], message, args[2] ? args.slice(2).join(' ') : undefined);
|
||||
if (page.status === true) {
|
||||
loading.delete();
|
||||
return this.success(message.channel, page.message);
|
||||
}
|
||||
loading.delete();
|
||||
return this.error(message.channel, page.message);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
|
||||
public logPage(sender: { number: string, user?: string }, recipient: { number: string, user?: string }, type: 'discord' | 'email' | 'phone', code: string): void {
|
||||
const chan = <TextableChannel> this.mainGuild.channels.get('722636436716781619');
|
||||
chan.createMessage(`***[${type.toUpperCase()}] \`${sender.number} (${sender.user ? sender.user : ''})\` sent a page to \`${recipient.number} (${recipient.user ? recipient.user : ''})\` with code \`${code}\`.***`);
|
||||
this.client.util.signale.log(`PAGE (${type.toUpperCase()})| TO: ${recipient.number}, FROM: ${sender.number}, CODE: ${code}`);
|
||||
}
|
||||
|
||||
public async page(recipientNumber: string, senderNumber: string, code: string, message: Message, txt?: string, options?: { emergencyNumber: string }): Promise<{status: boolean, message: string}> {
|
||||
try {
|
||||
if (txt?.length >= 140) return { status: false, message: 'Your message must be less than 141 characters.' };
|
||||
const senderEntry = await this.client.db.PagerNumber.findOne({ num: senderNumber });
|
||||
if (!senderEntry) {
|
||||
return { status: false, message: 'You do not have a Pager Number.' };
|
||||
}
|
||||
if (this.local.emergencyNumbers.includes(recipientNumber)) {
|
||||
switch (recipientNumber) {
|
||||
case '#0':
|
||||
this.local.departmentNumbers.forEach(async (num) => {
|
||||
await this.page(num, senderNumber, code, message, txt, { emergencyNumber: '0' });
|
||||
});
|
||||
break;
|
||||
case '#1':
|
||||
await this.page('00', senderNumber, code, message, txt, { emergencyNumber: '1' });
|
||||
await this.page('01', senderNumber, code, message, txt, { emergencyNumber: '1' });
|
||||
await this.page('10', senderNumber, code, message, txt, { emergencyNumber: '1' });
|
||||
await this.page('20', senderNumber, code, message, txt, { emergencyNumber: '1' });
|
||||
break;
|
||||
case '#2':
|
||||
const matthew = await this.client.db.PagerNumber.findOne({ individualAssignID: '278620217221971968' });
|
||||
const bsian = await this.client.db.PagerNumber.findOne({ individualAssignID: '253600545972027394' });
|
||||
const nightraven = await this.client.db.PagerNumber.findOne({ individualAssignID: '239261547959025665' });
|
||||
await this.page(matthew?.num, senderNumber, code, message, txt, { emergencyNumber: '2' });
|
||||
await this.page(bsian?.num, senderNumber, code, message, txt, { emergencyNumber: '2' });
|
||||
await this.page(nightraven?.num, senderNumber, code, message, txt, { emergencyNumber: '2' });
|
||||
await this.page('10', senderNumber, code, message, txt, { emergencyNumber: '2' });
|
||||
break;
|
||||
case '#3':
|
||||
await this.page('00', senderNumber, code, message, txt, { emergencyNumber: '3' });
|
||||
await this.page('01', senderNumber, code, message, txt, { emergencyNumber: '3' });
|
||||
await this.page('20', senderNumber, code, message, txt, { emergencyNumber: '3' });
|
||||
await this.page('21', senderNumber, code, message, txt, { emergencyNumber: '3' });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return { status: true, message: `Page to \`${recipientNumber}\` sent.` };
|
||||
}
|
||||
const recipientEntry = await this.client.db.PagerNumber.findOne({ num: recipientNumber });
|
||||
if (!recipientEntry) {
|
||||
return { status: false, message: `Pager Number \`${recipientNumber}\` does not exist.` };
|
||||
}
|
||||
if (!this.local.validPagerCodes.includes(code)) {
|
||||
return { status: false, message: 'The Pager Code you provided is invalid.' };
|
||||
}
|
||||
|
||||
for (const id of recipientEntry.discordIDs) {
|
||||
const recipient = this.mainGuild.members.get(recipientEntry.individualAssignID);
|
||||
const sender = this.mainGuild.members.get(senderEntry.individualAssignID);
|
||||
const chan = await this.client.getDMChannel(id);
|
||||
if (!chan) continue;
|
||||
if (!recipient || !sender) {
|
||||
this.logPage({ number: senderNumber, user: 'N/A' }, { number: recipientNumber, user: 'N/A' }, 'discord', code);
|
||||
} else {
|
||||
this.logPage({ number: senderNumber, user: `${sender.username}#${sender.discriminator}` }, { number: recipientNumber, user: `${recipient.username}#${recipient.discriminator}` }, 'discord', code);
|
||||
}
|
||||
chan.createMessage(`${options?.emergencyNumber ? `[SEN#${options.emergencyNumber}] ` : ''}__**Page**__\n**Recipient PN:** ${recipientNumber}\n**Sender PN:** ${senderNumber} (${sender ? `${sender.username}#${sender.discriminator}` : ''})\n**Initial Command:** https://discordapp.com/channels/${this.mainGuild.id}/${message.channel.id}/${message.id} (<#${message.channel.id}>)\n\n**Pager Code:** ${code} (${this.local.codeDict.get(code)})${txt ? `\n**Message:** ${txt}` : ''}`);
|
||||
}
|
||||
for (const email of recipientEntry.emailAddresses) {
|
||||
const recipient = this.mainGuild.members.get(recipientEntry.individualAssignID);
|
||||
const sender = this.mainGuild.members.get(senderEntry.individualAssignID);
|
||||
if (recipientEntry.receiveEmail === false) continue;
|
||||
if (!recipient || !sender) {
|
||||
this.logPage({ number: senderNumber, user: 'N/A' }, { number: recipientNumber, user: 'N/A' }, 'email', code);
|
||||
} else {
|
||||
this.logPage({ number: senderNumber, user: `${sender.username}#${sender.discriminator}` }, { number: recipientNumber, user: `${recipient.username}#${recipient.discriminator}` }, 'email', code);
|
||||
}
|
||||
await this.client.util.transporter.sendMail({
|
||||
from: '"LOC Paging System" <internal@libraryofcode.org>',
|
||||
to: email,
|
||||
subject: `PAGE FROM ${senderNumber}`,
|
||||
html: `<h1>Page</h1>${options?.emergencyNumber ? `<h2>[SEN#${options.emergencyNumber}]` : ''}<strong>Recipient PN:</strong> ${recipientNumber}<br><strong>Sender PN:</strong> ${senderNumber} (${sender ? `${sender.username}#${sender.discriminator}` : ''})<br><strong>Initial Command:</strong> https://discordapp.com/channels/${this.mainGuild.id}/${message.channel.id}/${message.id} (<#${message.channel.id}>)<br><br><strong>Pager Code:</strong> ${code} (${this.local.codeDict.get(code)})${txt ? `<br><strong>Message:</strong> ${txt}` : ''}`,
|
||||
});
|
||||
}
|
||||
|
||||
for (const id of recipientEntry.discordIDs) {
|
||||
const pager = await this.client.db.PagerNumber.findOne({ individualAssignID: id });
|
||||
if (!pager || !pager.receivePhone) continue;
|
||||
const member = await this.client.db.Staff.findOne({ userID: pager.individualAssignID });
|
||||
if (!member || !member.extension) continue;
|
||||
|
||||
const fileExtension = `${randomBytes(10).toString('hex')}`;
|
||||
|
||||
const [response] = await this.client.util.pbx.tts.synthesizeSpeech({
|
||||
input: { text: `Hello, this call was automatically dialed by the Library of Code Private Branch Exchange. You have received a page from Pager Number, ${senderNumber}. The Pager Code is ${code}. Please check your Direct Messages on Discord for further information.` },
|
||||
voice: { languageCode: 'en-US', ssmlGender: 'MALE' },
|
||||
audioConfig: { audioEncoding: 'OGG_OPUS' },
|
||||
});
|
||||
await fs.writeFile(`/tmp/${fileExtension}.ogg`, response.audioContent, 'binary');
|
||||
await this.client.util.exec(`ffmpeg -i /tmp/${fileExtension}.ogg -af "highpass=f=300, lowpass=f=3400" -ar 8000 -ac 1 -ab 64k -f mulaw /tmp/${fileExtension}.ulaw`);
|
||||
|
||||
try {
|
||||
const chan = await this.client.util.pbx.ari.channels.originate({
|
||||
endpoint: `PJSIP/${member.extension}`,
|
||||
extension: `${member.extension}`,
|
||||
callerId: `PAGE FRM ${senderNumber} <000>`,
|
||||
context: 'from-internal',
|
||||
priority: 1,
|
||||
app: 'cr-zero',
|
||||
});
|
||||
chan.on('StasisStart', async (_event, channel) => {
|
||||
const playback = await channel.play({
|
||||
media: `sound:/tmp/${fileExtension}`,
|
||||
}, undefined);
|
||||
playback.on('PlaybackFinished', () => {
|
||||
channel.hangup();
|
||||
});
|
||||
});
|
||||
const recipient = this.mainGuild.members.get(recipientEntry.individualAssignID);
|
||||
const sender = this.mainGuild.members.get(senderEntry.individualAssignID);
|
||||
|
||||
if (!recipient || !sender) {
|
||||
this.logPage({ number: senderNumber, user: 'N/A' }, { number: recipientNumber, user: 'N/A' }, 'phone', code);
|
||||
} else {
|
||||
this.logPage({ number: senderNumber, user: `${sender.username}#${sender.discriminator}` }, { number: recipientNumber, user: `${recipient.username}#${recipient.discriminator}` }, 'phone', code);
|
||||
}
|
||||
} catch (err) {
|
||||
this.client.util.signale.log(`Unable to Dial ${member.extension} | ${err}`);
|
||||
}
|
||||
}
|
||||
this.client.db.Stat.updateOne({ name: 'pages' }, { $inc: { value: 1 } }).exec();
|
||||
return { status: true, message: `Page to \`${recipientNumber}\` sent.` };
|
||||
} catch (err) {
|
||||
this.client.util.signale.error(err);
|
||||
return { status: false, message: `Error during Processing: ${err}` };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Ping extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'ping';
|
||||
this.description = 'Pings the bot';
|
||||
this.usage = 'ping';
|
||||
this.permissions = 0;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message) {
|
||||
try {
|
||||
const clientStart: number = Date.now();
|
||||
const msg: Message = await message.channel.createMessage('🏓 Pong!');
|
||||
msg.edit(`🏓 Pong!\nClient: \`${Date.now() - clientStart}ms\`\nResponse: \`${msg.createdAt - message.createdAt}ms\``);
|
||||
} catch (err) {
|
||||
this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
import Profile_Bio from './profile_bio';
|
||||
import Profile_GitHub from './profile_github';
|
||||
import Profile_Gitlab from './profile_gitlab';
|
||||
|
||||
export default class Profile extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'profile';
|
||||
this.description = 'Manages your profile on CR.';
|
||||
this.usage = 'profile <bio/github/gitlab> <new value>\n*Provide no value in subcommand to clear data.*';
|
||||
this.permissions = 0;
|
||||
this.enabled = true;
|
||||
this.subcmds = [Profile_Bio, Profile_GitHub, Profile_Gitlab];
|
||||
}
|
||||
|
||||
public async run(message: Message) {
|
||||
if (!await this.client.db.Member.exists({ userID: message.author.id })) {
|
||||
await this.client.db.Member.create({ userID: message.author.id });
|
||||
}
|
||||
|
||||
this.error(message.channel, `Please specify a valid option to change. Choose from \`github\`, \`bio\` and \`gitlab\`. You can view your profile with \`${this.client.config.prefix}whois\`.`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Profile_Bio extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'bio';
|
||||
this.description = 'Updates your bio on your profile.';
|
||||
this.usage = `${this.client.config.prefix}bio <new bio>`;
|
||||
this.permissions = 0;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
if (!await this.client.db.Member.exists({ userID: message.author.id })) {
|
||||
await this.client.db.Member.create({ userID: message.author.id });
|
||||
}
|
||||
const member = await this.client.db.Member.findOne({ userID: message.author.id });
|
||||
|
||||
if (!args[0]) {
|
||||
await member.updateOne({
|
||||
additional: {
|
||||
...member.additional,
|
||||
bio: null,
|
||||
},
|
||||
});
|
||||
return message.addReaction('modSuccess:578750988907970567');
|
||||
}
|
||||
const bio = args.join(' ');
|
||||
if (bio.length >= 256) return this.error(message.channel, 'Bio too long. It must be less than or equal to 256 characters.');
|
||||
await member.updateOne({
|
||||
additional: {
|
||||
...member.additional,
|
||||
bio,
|
||||
},
|
||||
});
|
||||
return message.addReaction('modSuccess:578750988907970567');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Profile_GitHub extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'github';
|
||||
this.description = 'Updates your GitHub information on your profile.';
|
||||
this.usage = `${this.client.config.prefix}github <GitHub profile URL>`;
|
||||
this.permissions = 0;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
if (!await this.client.db.Member.exists({ userID: message.author.id })) {
|
||||
await this.client.db.Member.create({ userID: message.author.id });
|
||||
}
|
||||
const member = await this.client.db.Member.findOne({ userID: message.author.id });
|
||||
if (!args[0]) {
|
||||
await member.updateOne({
|
||||
additional: {
|
||||
...member.additional,
|
||||
github: null,
|
||||
},
|
||||
});
|
||||
return message.addReaction('modSuccess:578750988907970567');
|
||||
}
|
||||
const urlRegex = new RegExp(
|
||||
'^(https?:\\/\\/)?'
|
||||
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'
|
||||
+ '((\\d{1,3}\\.){3}\\d{1,3}))'
|
||||
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'
|
||||
+ '(\\?[;&a-z\\d%_.~+=-]*)?'
|
||||
+ '(\\#[-a-z\\d_]*)?$',
|
||||
'i',
|
||||
);
|
||||
if (!urlRegex.test(args[0]) || !args[0].startsWith('https://github.com/')) return this.error(message.channel, 'Invalid GitHub profile URL.');
|
||||
|
||||
await member.updateOne({
|
||||
additional: {
|
||||
...member.additional,
|
||||
github: args[0],
|
||||
},
|
||||
});
|
||||
return message.addReaction('modSuccess:578750988907970567');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Profile_GitLab extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'gitlab';
|
||||
this.description = 'Updates your GitLab information on your profile.';
|
||||
this.usage = `${this.client.config.prefix}gitlab <GitLab profile URL>`;
|
||||
this.permissions = 0;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
if (!await this.client.db.Member.exists({ userID: message.author.id })) {
|
||||
await this.client.db.Member.create({ userID: message.author.id });
|
||||
}
|
||||
if (!args[0]) return this.error(message.channel, 'No GitLab profile URL was provided.');
|
||||
const member = await this.client.db.Member.findOne({ userID: message.author.id });
|
||||
if (!args[0]) {
|
||||
await member.updateOne({
|
||||
additional: {
|
||||
...member.additional,
|
||||
gitlab: null,
|
||||
},
|
||||
});
|
||||
return message.addReaction('modSuccess:578750988907970567');
|
||||
}
|
||||
const urlRegex = new RegExp(
|
||||
'^(https?:\\/\\/)?'
|
||||
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'
|
||||
+ '((\\d{1,3}\\.){3}\\d{1,3}))'
|
||||
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'
|
||||
+ '(\\?[;&a-z\\d%_.~+=-]*)?'
|
||||
+ '(\\#[-a-z\\d_]*)?$',
|
||||
'i',
|
||||
);
|
||||
if (!urlRegex.test(args[0])) return this.error(message.channel, 'Invalid GitLab profile URL.');
|
||||
|
||||
await member.updateOne({
|
||||
additional: {
|
||||
...member.additional,
|
||||
gitlab: args[0],
|
||||
},
|
||||
});
|
||||
return message.addReaction('modSuccess:578750988907970567');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/* eslint-disable no-continue */
|
||||
/* eslint-disable default-case */
|
||||
import { Message, TextChannel } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
import { getTotalMessageCount } from '../intervals/score';
|
||||
|
||||
export default class Score extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'pulldata';
|
||||
this.description = 'Retrieves information about a hard inquiry that was performed on a member.';
|
||||
this.usage = `${this.client.config.prefix}pulldata <userID> <reportID>`;
|
||||
this.aliases = ['inq'];
|
||||
this.permissions = 5;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[1]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
|
||||
const member = this.client.util.resolveMember(args[0], this.mainGuild);
|
||||
if (!member) return this.error(message.channel, 'Could not locate member.');
|
||||
const score = await this.client.db.Score.findOne({ userID: member.id });
|
||||
if (!score) return this.error(message.channel, 'Could not find Community Report for this user.');
|
||||
const report = score.inquiries.find((inq) => inq.id === args[1]);
|
||||
if (!report) return this.error(message.channel, 'Could not find inquiry information.');
|
||||
|
||||
await this.client.report.createInquiry(member.id, 'Library of Code sp-us | Bureau of Community Reports', 1);
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle(`Hard Inquiry Information - ${report.id}`);
|
||||
embed.setAuthor(member.username, member.user.avatarURL);
|
||||
let currentScore = '0';
|
||||
if (score.total < 200) currentScore = '---';
|
||||
else if (score.total > 800) currentScore = '800';
|
||||
else currentScore = `${score.total}`;
|
||||
embed.setDescription(`**Current Community Score:** ${currentScore}\n\n**Department/Service:** ${report.name || 'N/A'}\n**Reason:** ${report.reason || 'N/A'}`);
|
||||
|
||||
let totalScore = '0';
|
||||
let activityScore = '0';
|
||||
let moderationScore = '0';
|
||||
let roleScore = '0';
|
||||
let cloudServicesScore = '0';
|
||||
let otherScore = '0';
|
||||
let miscScore = '0';
|
||||
|
||||
if (score.total < 200) totalScore = '---';
|
||||
else if (score.total > 800) totalScore = '800';
|
||||
else totalScore = `${score.total}`;
|
||||
|
||||
if (score.activity < 10) activityScore = '---';
|
||||
else if (score.activity > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityScore = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12)));
|
||||
else activityScore = `${score.activity}`;
|
||||
|
||||
if (score.roles <= 0) roleScore = '---';
|
||||
else if (score.roles > 54) roleScore = '54';
|
||||
else roleScore = `${score.roles}`;
|
||||
|
||||
moderationScore = `${score.moderation}`;
|
||||
|
||||
if (score.other === 0) otherScore = '---';
|
||||
else otherScore = `${score.other}`;
|
||||
|
||||
if (score.staff <= 0) miscScore = '---';
|
||||
else miscScore = `${score.staff}`;
|
||||
|
||||
if (score.cloudServices === 0) cloudServicesScore = '---';
|
||||
else if (score.cloudServices > 10) cloudServicesScore = '10';
|
||||
else cloudServicesScore = `${score.cloudServices}`;
|
||||
|
||||
let color = '🔴';
|
||||
let additionalText = 'POOR';
|
||||
embed.setColor('FF0000');
|
||||
if (score.total >= 550) { color = '🟠'; additionalText = 'FAIR'; embed.setColor('FFA500'); }
|
||||
if (score.total >= 630) { color = '🟡'; additionalText = 'GOOD'; embed.setColor('FFFF00'); }
|
||||
if (score.total >= 700) { color = '🟢'; additionalText = 'EXCELLENT'; embed.setColor('66FF66'); }
|
||||
if (score.total >= 770) { color = '✨'; additionalText = 'EXCEPTIONAL'; embed.setColor('#99FFFF'); }
|
||||
embed.addField('Total | 200 to 800', score ? `${color} ${totalScore} | ${additionalText}` : 'N/C', true);
|
||||
embed.addField(`Activity | 10 to ${Math.floor(Math.log1p(getTotalMessageCount(this.client)) * 12)}`, activityScore || 'N/C', true);
|
||||
embed.addField('Roles | 1 to N/A', roleScore || 'N/C', true);
|
||||
embed.addField('Moderation | N/A to 2' || 'N/C', moderationScore, true);
|
||||
embed.addField('Cloud Services | N/A to 10+', cloudServicesScore || 'N/C', true);
|
||||
embed.addField('Other', otherScore || 'N/C', true);
|
||||
embed.addField('Misc', miscScore || 'N/C', true);
|
||||
if (score.pin?.length > 0) {
|
||||
embed.addField('PIN', score.pin.join('-'), true);
|
||||
}
|
||||
embed.setTimestamp(report.date);
|
||||
embed.setFooter('Inquiry performed on', this.client.user.avatarURL);
|
||||
return message.channel.createMessage({ embed });
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import { Message, Role } from 'eris';
|
||||
import { createPaginationEmbed } from 'eris-pagination';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class Rank extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'rank';
|
||||
this.description = 'Joins/leaves a self-assignable rank. Run this command without arguments to get a list of self-assignable roles.';
|
||||
this.usage = `${this.client.config.prefix}rank <rank>`;
|
||||
this.permissions = 0;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) {
|
||||
const roles = await this.client.db.Rank.find();
|
||||
const rankArray: [{ name: string, value: string }?] = [];
|
||||
for (const rank of roles.sort((a, b) => a.name.localeCompare(b.name))) {
|
||||
let perms: string;
|
||||
if (rank.permissions.includes('0')) {
|
||||
perms = 'Everyone';
|
||||
} else {
|
||||
const rolesArray: Role[] = [];
|
||||
rank.permissions.forEach((r) => {
|
||||
rolesArray.push(this.mainGuild.roles.get(r));
|
||||
});
|
||||
perms = rolesArray.map((r) => this.mainGuild.roles.get(r.id)).sort((a, b) => b.position - a.position).map((r) => `<@&${r.id}>`).join(', ');
|
||||
}
|
||||
let hasRank = false;
|
||||
if (message.member.roles.includes(rank.roleID)) hasRank = true;
|
||||
rankArray.push({ name: rank.name, value: `${hasRank ? '*You have this role.*\n' : ''}__Description:__ ${rank.description}\n__Permissions:__ ${perms}` });
|
||||
}
|
||||
const ranksSplit = this.client.util.splitFields(rankArray);
|
||||
const cmdPages: RichEmbed[] = [];
|
||||
ranksSplit.forEach((split) => {
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Ranks');
|
||||
embed.setDescription(`Use \`${this.client.config.prefix}rank <rank name>\` to join/leave the rank.`);
|
||||
embed.setFooter(`Requested by: ${message.author.username}#${message.author.discriminator} | ${this.client.user.username}`, message.author.avatarURL);
|
||||
embed.setTimestamp();
|
||||
split.forEach((c) => embed.addField(c.name, c.value));
|
||||
return cmdPages.push(embed);
|
||||
});
|
||||
if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] });
|
||||
return createPaginationEmbed(message, cmdPages);
|
||||
}
|
||||
const role = this.client.util.resolveRole(args.join(' '), this.client.guilds.get(this.client.config.guildID));
|
||||
if (!role) return this.error(message.channel, 'The role you specified doesn\'t exist.');
|
||||
const entry = await this.client.db.Rank.findOne({ roleID: role.id }).lean().exec();
|
||||
if (!entry) return this.error(message.channel, 'The rank you specified doesn\'t exist.');
|
||||
if (!message.member.roles.includes(entry.roleID)) {
|
||||
let permCheck: boolean;
|
||||
if (entry.permissions.includes('0')) {
|
||||
permCheck = true;
|
||||
} else {
|
||||
permCheck = entry.permissions.some((item) => message.member.roles.includes(item));
|
||||
}
|
||||
if (!permCheck) return this.error(message.channel, 'Permission denied.');
|
||||
await message.member.addRole(entry.roleID, 'User self-assigned this role.');
|
||||
this.success(message.channel, `You have self-assigned ${entry.name}.`);
|
||||
} else if (message.member.roles.includes(entry.roleID)) {
|
||||
await message.member.removeRole(entry.roleID, 'User has removed a self-assignable role.');
|
||||
this.success(message.channel, `You have removed ${entry.name}.`);
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import { Message, Role as DRole } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Role extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'role';
|
||||
this.description = 'Manage the roles of a member.';
|
||||
this.usage = `${this.client.config.prefix}role <user> <roles>`;
|
||||
this.permissions = 6;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (args.length < 2) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const member = this.client.util.resolveMember(args[0], this.mainGuild);
|
||||
if (!member) return this.error(message.channel, 'Member not found');
|
||||
// if (!this.client.util.moderation.checkPermissions(member, message.member)) return this.error(message.channel, 'Permission denied.');
|
||||
const rolesList = args.slice(1).join(' ').split(', ');
|
||||
const rolesToAdd = [];
|
||||
const rolesToRemove = [];
|
||||
let stop = false;
|
||||
for (const arg of rolesList) {
|
||||
const action = arg[0];
|
||||
let role: DRole;
|
||||
if (action !== '+' && action !== '-') {
|
||||
role = this.client.util.resolveRole(arg, this.mainGuild);
|
||||
if (!role) {
|
||||
stop = true;
|
||||
return this.error(message.channel, `Role \`${arg}\` not found.`);
|
||||
}
|
||||
if (member.roles.includes(role.id)) return rolesToRemove.push(role);
|
||||
rolesToAdd.push(role);
|
||||
continue;
|
||||
}
|
||||
if (action === '+') {
|
||||
role = this.client.util.resolveRole(arg.slice(1), this.mainGuild);
|
||||
if (!role) {
|
||||
stop = true;
|
||||
return this.error(message.channel, `Role \`${arg.slice(1)}\` not found.`);
|
||||
}
|
||||
if (member.roles.includes(role.id)) {
|
||||
stop = true;
|
||||
return this.error(message.channel, `You already have the role \`${role.name}\`.`);
|
||||
}
|
||||
rolesToAdd.push(role);
|
||||
continue;
|
||||
}
|
||||
if (action === '-') {
|
||||
role = this.client.util.resolveRole(arg.slice(1), this.mainGuild);
|
||||
if (!role) {
|
||||
stop = true;
|
||||
return this.error(message.channel, `Role \`${arg.slice(1)}\` not found.`);
|
||||
}
|
||||
if (!member.roles.includes(role.id)) {
|
||||
stop = true;
|
||||
return this.error(message.channel, `You don't have the role \`${role.name}\``);
|
||||
}
|
||||
rolesToRemove.push(role);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
// if (stop) return;
|
||||
rolesToAdd.forEach((role) => member.addRole(role.id));
|
||||
rolesToRemove.forEach((role) => member.removeRole(role.id));
|
||||
return this.success(message.channel, `Changed the roles for ${member.username}#${member.discriminator}${rolesToAdd.length > 0 ? `, added \`${rolesToAdd.map((r) => r.name).join('`, `')}\`` : ''}${rolesToRemove.length > 0 ? `, removed \`${rolesToRemove.map((r) => r.name).join('`, `')}\`` : ''}`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import moment from 'moment';
|
||||
import { Message, Role } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class Roleinfo extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'roleinfo';
|
||||
this.description = 'Get information about a role.';
|
||||
this.usage = 'roleinfo [role ID or role name]';
|
||||
this.permissions = 0;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
|
||||
const role = this.client.util.resolveRole(args.join(' '), this.mainGuild);
|
||||
if (!role) return this.error(message.channel, 'Could not find role.');
|
||||
|
||||
const perms = role.permissions;
|
||||
const permsArray: string[] = [];
|
||||
if (perms.has('administrator')) permsArray.push('Administrator');
|
||||
if (perms.has('manageGuild')) permsArray.push('Manage Server');
|
||||
if (perms.has('manageChannels')) permsArray.push('Manage Channels');
|
||||
if (perms.has('manageRoles')) permsArray.push('Manage Roles');
|
||||
if (perms.has('manageMessages')) permsArray.push('Manage Messages');
|
||||
if (perms.has('manageNicknames')) permsArray.push('Manage Nicknames');
|
||||
if (perms.has('manageEmojis')) permsArray.push('Manage Emojis');
|
||||
if (perms.has('banMembers')) permsArray.push('Ban Members');
|
||||
if (perms.has('kickMembers')) permsArray.push('Kick Members');
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Role Information');
|
||||
embed.setDescription(`<@&${role.id}> ID: \`${role.id}\``);
|
||||
embed.setColor(role.color);
|
||||
embed.addField('Name', role.name, true);
|
||||
embed.addField('Color', role.color ? this.client.util.decimalToHex(role.color) : 'None', true);
|
||||
embed.addField('Members', String(this.client.guilds.get(this.client.config.guildID).members.filter((m) => m.roles.includes(role.id)).length), true);
|
||||
embed.addField('Hoisted', role.hoist ? 'Yes' : 'No', true);
|
||||
embed.addField('Position', String(role.position), true);
|
||||
embed.addField('Creation Date', `${moment(new Date(role.createdAt)).format('dddd, MMMM Do YYYY, h:mm:ss A')} ET`, true);
|
||||
embed.addField('Mentionable', role.mentionable ? 'Yes' : 'No', true);
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
|
||||
if (permsArray.length > 0) {
|
||||
embed.addField('Permissions', permsArray.join(', '), true);
|
||||
}
|
||||
return message.channel.createMessage({ embed });
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/* eslint-disable no-plusplus */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable default-case */
|
||||
import moment from 'moment';
|
||||
import { Message, User } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
import { getTotalMessageCount } from '../intervals/score';
|
||||
import Score_Hist from './score_hist';
|
||||
import Score_Notify from './score_notify';
|
||||
import Score_Pref from './score_pref';
|
||||
|
||||
export default class Score extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'score';
|
||||
this.description = 'Retrieves your Community Report';
|
||||
this.usage = `${this.client.config.prefix}score\n${this.client.config.prefix}score <member> <type: 'hard' | 'soft'> <reporting department: ex. Library of Code sp-us | Cloud Account Services>:<reason>`;
|
||||
this.aliases = ['report'];
|
||||
this.subcmds = [Score_Hist, Score_Notify, Score_Pref];
|
||||
this.permissions = 0;
|
||||
this.guildOnly = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
let check = false;
|
||||
let user: User;
|
||||
if (!args[0] || !this.checkCustomPermissions(this.client.util.resolveMember(message.author.id, this.mainGuild), 4)) {
|
||||
user = message.author;
|
||||
if (!user) return this.error(message.channel, 'Member not found.');
|
||||
await this.client.report.createInquiry(user.id, `${user.username} via Discord`, 1);
|
||||
check = true;
|
||||
} else {
|
||||
user = this.client.util.resolveMember(args[0], this.mainGuild)?.user;
|
||||
if (!user) {
|
||||
const sc = await this.client.db.Score.findOne({ pin: [Number(args[0].split('-')[0]), Number(args[0].split('-')[1]), Number(args[0].split('-')[2])] });
|
||||
if (!sc) return this.error(message.channel, 'Member not found.');
|
||||
user = this.client.util.resolveMember(sc.userID, this.mainGuild)?.user;
|
||||
}
|
||||
if (!user) return this.error(message.channel, 'Member not found.');
|
||||
if (message.channel.type !== 0) return this.error(message.channel, 'Hard Inquiries must be initiated in a guild.');
|
||||
if (args[1] === 'hard') {
|
||||
if (args.length < 3) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const name = args.slice(2).join(' ').split(':')[0];
|
||||
const reason = args.slice(2).join(' ').split(':')[1];
|
||||
const score = await this.client.db.Score.findOne({ userID: user.id });
|
||||
if (!score) return this.error(message.channel, 'Score not calculated yet.');
|
||||
if (score.locked) return this.error(message.channel, 'The score report you have requested has been locked.');
|
||||
await this.client.report.createInquiry(score.userID, name, 0, reason);
|
||||
}
|
||||
}
|
||||
if (!user) return this.error(message.channel, 'Member not found.');
|
||||
const score = await this.client.db.Score.findOne({ userID: user.id });
|
||||
const inqs = await this.client.db.Inquiry.find({ userID: user.id, type: 0 });
|
||||
if (!score) return this.error(message.channel, 'Community Report has not been generated yet.');
|
||||
let totalScore = '0';
|
||||
let activityScore = '0';
|
||||
let moderationScore = '0';
|
||||
let roleScore = '0';
|
||||
let cloudServicesScore = '0';
|
||||
let otherScore = '0';
|
||||
let miscScore = '0';
|
||||
|
||||
if (score) {
|
||||
if (score.total < 200) totalScore = '---';
|
||||
else if (score.total > 800) totalScore = '800';
|
||||
else totalScore = `${score.total}`;
|
||||
|
||||
if (score.activity < 10) activityScore = '---';
|
||||
else if (score.activity > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityScore = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12)));
|
||||
else activityScore = `${score.activity}`;
|
||||
|
||||
if (score.roles <= 0) roleScore = '---';
|
||||
else if (score.roles > 54) roleScore = '54';
|
||||
else roleScore = `${score.roles}`;
|
||||
|
||||
moderationScore = `${score.moderation}`;
|
||||
|
||||
if (score.other === 0) otherScore = '---';
|
||||
else otherScore = `${score.other}`;
|
||||
|
||||
if (score.staff <= 0) miscScore = '---';
|
||||
else miscScore = `${score.staff}`;
|
||||
|
||||
if (score.cloudServices === 0) cloudServicesScore = '---';
|
||||
else if (score.cloudServices > 10) cloudServicesScore = '10';
|
||||
else cloudServicesScore = `${score.cloudServices}`;
|
||||
} // else return this.error(message.channel, 'Community Score has not been calculated yet.');
|
||||
|
||||
const set = [];
|
||||
const accounts = await this.client.db.Score.find().lean().exec();
|
||||
for (const sc of accounts) {
|
||||
if (sc.total < 200) { continue; }
|
||||
if (sc.total > 800) { set.push(800); continue; }
|
||||
set.push(sc.total);
|
||||
}
|
||||
const percentile = this.client.util.percentile(set, score.total);
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Community Report');
|
||||
embed.setAuthor(user.username, user.avatarURL);
|
||||
embed.setThumbnail(user.avatarURL);
|
||||
/* for (const role of member.roles.map((r) => this.mainGuild.roles.get(r)).sort((a, b) => b.position - a.position)) {
|
||||
if (role?.color !== 0) {
|
||||
embed.setColor(role.color);
|
||||
break;
|
||||
}
|
||||
} */
|
||||
let inqAdded = 0;
|
||||
if (inqs?.length > 0) {
|
||||
let desc = '__**Hard Inquiries**__\n*These inquiries will fall off your report within 2 months, try to keep your hard inquiries to a minimum. If you want to file a dispute, please DM Ramirez.*\n\n';
|
||||
inqs.forEach((inq) => {
|
||||
const testDate = (new Date(new Date(inq.date).setHours(1460)));
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
if (testDate > new Date()) {
|
||||
desc += `${inq.iid ? `__[${inq.iid}]__\n` : ''}**Department/Service:** ${inq.name.replace(/\*/gmi, '')}\n**Reason:** ${inq.reason}\n**Date:** ${moment(inq.date).format('MMMM Do YYYY, h:mm:ss')}\n**Expires:** ${moment(testDate).calendar()}\n\n`;
|
||||
inqAdded++;
|
||||
}
|
||||
});
|
||||
if (inqAdded <= 0) desc = '';
|
||||
if (desc.length >= 1900) {
|
||||
embed.setDescription('__***Too many Hard Inquiries to show, please see https://report.libraryofcode.org***__');
|
||||
} else {
|
||||
embed.setDescription(desc);
|
||||
}
|
||||
}
|
||||
let color = '🔴';
|
||||
let additionalText = 'POOR';
|
||||
embed.setColor('FF0000');
|
||||
if (score.total >= 550) { color = '🟠'; additionalText = 'FAIR'; embed.setColor('FFA500'); }
|
||||
if (score.total >= 630) { color = '🟡'; additionalText = 'GOOD'; embed.setColor('FFFF00'); }
|
||||
if (score.total >= 700) { color = '🟢'; additionalText = 'EXCELLENT'; embed.setColor('66FF66'); }
|
||||
if (score.total >= 770) { color = '✨'; additionalText = 'EXCEPTIONAL'; embed.setColor('#99FFFF'); }
|
||||
embed.addField('CommScore™ | 200 to 800', score ? `${color} ${totalScore} | ${additionalText} | ${this.client.util.ordinal(Math.round(percentile))} Percentile` : 'N/C', true);
|
||||
embed.addField(`Activity | 10 to ${Math.floor(Math.log1p(getTotalMessageCount(this.client)) * 12)}`, activityScore || 'N/C', true);
|
||||
embed.addField('Roles | 1 to N/A', roleScore || 'N/C', true);
|
||||
embed.addField('Moderation | N/A to 2' || 'N/C', moderationScore, true);
|
||||
embed.addField('Cloud Services | N/A to 10+', cloudServicesScore || 'N/C', true);
|
||||
embed.addField('Other', otherScore || 'N/C', true);
|
||||
embed.addField('Misc', miscScore || 'N/C', true);
|
||||
if (score.locked) {
|
||||
embed.addField('Status', 'Score Report Locked');
|
||||
}
|
||||
if (score.pin?.length > 0 && message.channel.type === 1) {
|
||||
embed.addField('PIN', score.pin.join('-'), true);
|
||||
}
|
||||
if (score.lastUpdate) {
|
||||
embed.setFooter('Report last updated', this.client.user.avatarURL);
|
||||
embed.setTimestamp(score.lastUpdate);
|
||||
} else {
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
}
|
||||
// eslint-disable-next-line no-mixed-operators
|
||||
if ((args[1] === 'hard' || args[1] === 'soft') && this.checkCustomPermissions(this.client.util.resolveMember(message.author.id, this.mainGuild), 4)) {
|
||||
if (score.pin?.length > 0) embed.addField('PIN', score.pin.join('-'), true);
|
||||
|
||||
if (args[1] === 'soft' && !check) {
|
||||
let name = '';
|
||||
for (const role of this.client.util.resolveMember(message.author.id, this.mainGuild).roles.map((r) => this.mainGuild.roles.get(r)).sort((a, b) => b.position - a.position)) {
|
||||
name = `Library of Code sp-us | ${role.name}`;
|
||||
break;
|
||||
}
|
||||
await this.client.report.createInquiry(user.id, name, 1);
|
||||
}
|
||||
}
|
||||
if (args[1] === 'hard' && this.checkCustomPermissions(this.client.util.resolveMember(message.author.id, this.mainGuild), 6)) {
|
||||
await message.channel.createMessage({ embed });
|
||||
await message.channel.createMessage('***===BEGIN ADDITIONAL INFORMATION===***');
|
||||
await this.client.commands.get('score').subcommands.get('hist').run(message, [user.id]);
|
||||
await this.client.commands.get('whois').run(message, [user.id]);
|
||||
await this.client.commands.get('notes').run(message, [user.id]);
|
||||
const whoisMessage = await message.channel.createMessage(`=whois ${user.id} --full`);
|
||||
await whoisMessage.delete();
|
||||
const modlogsMessage = await message.channel.createMessage(`=modlogs ${user.id}`);
|
||||
await modlogsMessage.delete();
|
||||
return await message.channel.createMessage('***===END ADDITIONAL INFORMATION===***');
|
||||
}
|
||||
return message.channel.createMessage({ embed });
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,334 @@
|
|||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable default-case */
|
||||
import moment from 'moment';
|
||||
import { median, mode, mean } from 'mathjs';
|
||||
import { createPaginationEmbed } from 'eris-pagination';
|
||||
import { Message, User, TextChannel } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
import { getTotalMessageCount } from '../intervals/score';
|
||||
import { InquiryInterface } from '../models';
|
||||
|
||||
export default class Score_Hist extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'hist';
|
||||
this.description = 'Pulls your Community Report history.';
|
||||
this.usage = `${this.client.config.prefix}score hist <member>\n${this.client.config.prefix}score hist`;
|
||||
this.permissions = 0;
|
||||
this.guildOnly = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
let user: User;
|
||||
|
||||
if (!args[0] || !this.checkCustomPermissions(this.client.util.resolveMember(message.author.id, this.mainGuild), 4)) {
|
||||
user = message.author;
|
||||
if (!user) return this.error(message.channel, 'Member not found.');
|
||||
const hists = await this.client.db.ScoreHistorical.find({ userID: user.id }).lean().exec();
|
||||
if (!hists) return this.error(message.channel, 'No history found.');
|
||||
if (hists.length < 1) return this.error(message.channel, 'No history found.');
|
||||
const histArray: [{ name: string, value: string }?] = [];
|
||||
const totalArray: number[] = [];
|
||||
const activityArray: number[] = [];
|
||||
const moderationArray: number[] = [];
|
||||
const roleArray: number[] = [];
|
||||
const cloudServicesArray: number[] = [];
|
||||
const otherArray: number[] = [];
|
||||
const miscArray: number[] = [];
|
||||
for (const hist of hists.reverse()) {
|
||||
totalArray.push(hist.report.total);
|
||||
activityArray.push(hist.report.activity);
|
||||
moderationArray.push(hist.report.moderation);
|
||||
roleArray.push(hist.report.roles);
|
||||
cloudServicesArray.push(hist.report.cloudServices);
|
||||
otherArray.push(hist.report.other);
|
||||
miscArray.push(hist.report.staff);
|
||||
let totalScore = '0';
|
||||
let activityScore = '0';
|
||||
let moderationScore = '0';
|
||||
let roleScore = '0';
|
||||
let cloudServicesScore = '0';
|
||||
let otherScore = '0';
|
||||
let miscScore = '0';
|
||||
|
||||
if (hist.report.total < 200) totalScore = '---';
|
||||
else if (hist.report.total > 800) totalScore = '800';
|
||||
else totalScore = `${hist.report.total}`;
|
||||
|
||||
if (hist.report.activity < 10) activityScore = '---';
|
||||
else if (hist.report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityScore = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12)));
|
||||
else activityScore = `${hist.report.activity}`;
|
||||
|
||||
if (hist.report.roles <= 0) roleScore = '---';
|
||||
else if (hist.report.roles > 54) roleScore = '54';
|
||||
else roleScore = `${hist.report.roles}`;
|
||||
|
||||
moderationScore = `${hist.report.moderation}`;
|
||||
|
||||
if (hist.report.other === 0) otherScore = '---';
|
||||
else otherScore = `${hist.report.other}`;
|
||||
|
||||
if (hist.report.staff <= 0) miscScore = '---';
|
||||
else miscScore = `${hist.report.staff}`;
|
||||
|
||||
if (hist.report.cloudServices === 0) cloudServicesScore = '---';
|
||||
else if (hist.report.cloudServices > 10) cloudServicesScore = '10';
|
||||
else cloudServicesScore = `${hist.report.cloudServices}`;
|
||||
|
||||
let data = '';
|
||||
let hardInquiries = 0;
|
||||
const inquiries: InquiryInterface[] = [];
|
||||
if (hist.inquiries?.length > 0) {
|
||||
for (const h of hist.inquiries) {
|
||||
const inq = await this.client.db.Inquiry.findOne({ _id: h });
|
||||
inquiries.push(inq);
|
||||
}
|
||||
}
|
||||
if (inquiries?.length > 0) {
|
||||
inquiries.forEach((inq) => {
|
||||
const testDate = (new Date(new Date(inq.date).setHours(1460)));
|
||||
// eslint-disable-next-line no-plusplus
|
||||
if (testDate > new Date()) hardInquiries++;
|
||||
});
|
||||
data += `__CommScore™:__ ${totalScore}\n__Activity:__ ${activityScore}\n__Roles:__ ${roleScore}\n__Moderation:__ ${moderationScore}\n__Cloud Services:__ ${cloudServicesScore}\n__Other:__ ${otherScore}\n__Misc:__ ${miscScore}\n\n__Hard Inquiries:__ ${hardInquiries}\n__Soft Inquiries:__ ${hist.report.softInquiries?.length ?? '0'}`;
|
||||
histArray.push({ name: moment(hist.date).calendar(), value: data });
|
||||
}
|
||||
}
|
||||
|
||||
const stat = {
|
||||
totalMean: mean(totalArray),
|
||||
totalMode: mode(totalArray),
|
||||
totalMedian: median(totalArray),
|
||||
activityMean: mean(activityArray),
|
||||
rolesMean: mean(roleArray),
|
||||
moderationMean: mean(moderationArray),
|
||||
cloudServicesMean: mean(cloudServicesArray),
|
||||
otherMean: mean(otherArray),
|
||||
miscMean: mean(miscArray),
|
||||
};
|
||||
const splitHist = this.client.util.splitFields(histArray);
|
||||
const cmdPages: RichEmbed[] = [];
|
||||
splitHist.forEach((split) => {
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Historical Community Report');
|
||||
let totalMean = '0';
|
||||
let totalMedian = '0';
|
||||
let totalMode = '0';
|
||||
let activityMean = '0';
|
||||
let moderationMean = '0';
|
||||
let roleMean = '0';
|
||||
let cloudServicesMean = '0';
|
||||
let otherMean = '0';
|
||||
let miscMean = '0';
|
||||
|
||||
if (stat.totalMean < 200) totalMean = '---';
|
||||
else if (stat.totalMean > 800) totalMean = '800';
|
||||
else totalMean = `${stat.totalMean}`;
|
||||
|
||||
if (stat.totalMedian < 200) totalMedian = '---';
|
||||
else if (stat.totalMedian > 800) totalMedian = '800';
|
||||
else totalMedian = `${stat.totalMedian}`;
|
||||
|
||||
if (stat.totalMode < 200) totalMode = '---';
|
||||
else if (stat.totalMode > 800) totalMode = '800';
|
||||
else totalMode = `${stat.totalMode}`;
|
||||
|
||||
if (stat.activityMean < 10) activityMean = '---';
|
||||
else if (stat.activityMean > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityMean = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12)));
|
||||
else activityMean = `${stat.activityMean}`;
|
||||
|
||||
if (stat.rolesMean <= 0) roleMean = '---';
|
||||
else if (stat.rolesMean > 54) roleMean = '54';
|
||||
else roleMean = `${stat.rolesMean}`;
|
||||
|
||||
moderationMean = `${stat.moderationMean}`;
|
||||
|
||||
if (stat.otherMean === 0) otherMean = '---';
|
||||
else otherMean = `${stat.otherMean}`;
|
||||
|
||||
if (stat.miscMean <= 0) miscMean = '---';
|
||||
else miscMean = `${stat.miscMean}`;
|
||||
|
||||
if (stat.cloudServicesMean === 0) cloudServicesMean = '---';
|
||||
else if (stat.cloudServicesMean > 10) cloudServicesMean = '10';
|
||||
else cloudServicesMean = `${stat.cloudServicesMean}`;
|
||||
|
||||
embed.setDescription(`__**Statistical Averages**__\n**CommScore™ Mean:** ${totalMean} | **CommScore™ Mode:** ${totalMode} | **CommScore™ Median:** ${totalMedian}\n\n**Activity Mean:** ${activityMean}\n**Roles Mean:** ${roleMean}\n**Moderation Mean:** ${moderationMean}\n**Cloud Services Mean:** ${cloudServicesMean}\n**Other Mean:** ${otherMean}\n**Misc Mean:** ${miscMean}`);
|
||||
embed.setAuthor(user.username, user.avatarURL);
|
||||
embed.setThumbnail(user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
split.forEach((c) => embed.addField(c.name, c.value));
|
||||
return cmdPages.push(embed);
|
||||
});
|
||||
const name = `${user.username} VIA DISCORD - [HISTORICAL]`;
|
||||
await this.client.report.createInquiry(user.id, name, 1);
|
||||
|
||||
|
||||
if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] });
|
||||
return createPaginationEmbed(message, cmdPages);
|
||||
} if (args[0] && this.checkCustomPermissions(this.client.util.resolveMember(message.author.id, this.mainGuild), 4)) {
|
||||
user = this.client.util.resolveMember(args[0], this.mainGuild)?.user;
|
||||
if (!user) {
|
||||
const sc = await this.client.db.Score.findOne({ pin: [Number(args[0].split('-')[0]), Number(args[0].split('-')[1]), Number(args[0].split('-')[2])] });
|
||||
user = this.client.util.resolveMember(sc.userID, this.mainGuild)?.user;
|
||||
}
|
||||
if (!user) return this.error(message.channel, 'Member not found.');
|
||||
const hists = await this.client.db.ScoreHistorical.find({ userID: user.id }).lean().exec();
|
||||
if (!hists) return this.error(message.channel, 'No history found.');
|
||||
if (hists.length < 1) return this.error(message.channel, 'No history found.');
|
||||
const histArray: [{ name: string, value: string }?] = [];
|
||||
const totalArray: number[] = [];
|
||||
const activityArray: number[] = [];
|
||||
const moderationArray: number[] = [];
|
||||
const roleArray: number[] = [];
|
||||
const cloudServicesArray: number[] = [];
|
||||
const otherArray: number[] = [];
|
||||
const miscArray: number[] = [];
|
||||
for (const hist of hists.reverse()) {
|
||||
totalArray.push(hist.report.total);
|
||||
activityArray.push(hist.report.activity);
|
||||
moderationArray.push(hist.report.moderation);
|
||||
roleArray.push(hist.report.roles);
|
||||
cloudServicesArray.push(hist.report.cloudServices);
|
||||
otherArray.push(hist.report.other);
|
||||
miscArray.push(hist.report.staff);
|
||||
let totalScore = '0';
|
||||
let activityScore = '0';
|
||||
let moderationScore = '0';
|
||||
let roleScore = '0';
|
||||
let cloudServicesScore = '0';
|
||||
let otherScore = '0';
|
||||
let miscScore = '0';
|
||||
|
||||
if (hist.report.total < 200) totalScore = '---';
|
||||
else if (hist.report.total > 800) totalScore = '800';
|
||||
else totalScore = `${hist.report.total}`;
|
||||
|
||||
if (hist.report.activity < 10) activityScore = '---';
|
||||
else if (hist.report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityScore = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12)));
|
||||
else activityScore = `${hist.report.activity}`;
|
||||
|
||||
if (hist.report.roles <= 0) roleScore = '---';
|
||||
else if (hist.report.roles > 54) roleScore = '54';
|
||||
else roleScore = `${hist.report.roles}`;
|
||||
|
||||
moderationScore = `${hist.report.moderation}`;
|
||||
|
||||
if (hist.report.other === 0) otherScore = '---';
|
||||
else otherScore = `${hist.report.other}`;
|
||||
|
||||
if (hist.report.staff <= 0) miscScore = '---';
|
||||
else miscScore = `${hist.report.staff}`;
|
||||
|
||||
if (hist.report.cloudServices === 0) cloudServicesScore = '---';
|
||||
else if (hist.report.cloudServices > 10) cloudServicesScore = '10';
|
||||
else cloudServicesScore = `${hist.report.cloudServices}`;
|
||||
|
||||
let data = '';
|
||||
let hardInquiries = 0;
|
||||
const inquiries: InquiryInterface[] = [];
|
||||
if (hist.inquiries?.length > 0) {
|
||||
for (const h of hist.inquiries) {
|
||||
const inq = await this.client.db.Inquiry.findOne({ _id: h });
|
||||
inquiries.push(inq);
|
||||
}
|
||||
}
|
||||
if (inquiries?.length > 0) {
|
||||
inquiries.forEach((inq) => {
|
||||
const testDate = (new Date(new Date(inq.date).setHours(1460)));
|
||||
// eslint-disable-next-line no-plusplus
|
||||
if (testDate > new Date()) hardInquiries++;
|
||||
});
|
||||
data += `__CommScore™:__ ${totalScore}\n__Activity:__ ${activityScore}\n__Roles:__ ${roleScore}\n__Moderation:__ ${moderationScore}\n__Cloud Services:__ ${cloudServicesScore}\n__Other:__ ${otherScore}\n__Misc:__ ${miscScore}\n\n__Hard Inquiries:__ ${hardInquiries}\n__Soft Inquiries:__ ${hist.report.softInquiries?.length ?? '0'}`;
|
||||
histArray.push({ name: moment(hist.date).calendar(), value: data });
|
||||
}
|
||||
}
|
||||
|
||||
const stat = {
|
||||
totalMean: mean(totalArray),
|
||||
totalMode: mode(totalArray),
|
||||
totalMedian: median(totalArray),
|
||||
activityMean: mean(activityArray),
|
||||
rolesMean: mean(roleArray),
|
||||
moderationMean: mean(moderationArray),
|
||||
cloudServicesMean: mean(cloudServicesArray),
|
||||
otherMean: mean(otherArray),
|
||||
miscMean: mean(miscArray),
|
||||
};
|
||||
const splitHist = this.client.util.splitFields(histArray);
|
||||
const cmdPages: RichEmbed[] = [];
|
||||
splitHist.forEach((split) => {
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Historical Community Report');
|
||||
let totalMean = '0';
|
||||
let totalMedian = '0';
|
||||
let totalMode = '0';
|
||||
let activityMean = '0';
|
||||
let moderationMean = '0';
|
||||
let roleMean = '0';
|
||||
let cloudServicesMean = '0';
|
||||
let otherMean = '0';
|
||||
let miscMean = '0';
|
||||
|
||||
if (stat.totalMean < 200) totalMean = '---';
|
||||
else if (stat.totalMean > 800) totalMean = '800';
|
||||
else totalMean = `${stat.totalMean}`;
|
||||
|
||||
if (stat.totalMedian < 200) totalMedian = '---';
|
||||
else if (stat.totalMedian > 800) totalMedian = '800';
|
||||
else totalMedian = `${stat.totalMedian}`;
|
||||
|
||||
if (stat.totalMode < 200) totalMode = '---';
|
||||
else if (stat.totalMode > 800) totalMode = '800';
|
||||
else totalMode = `${stat.totalMode}`;
|
||||
|
||||
if (stat.activityMean < 10) activityMean = '---';
|
||||
else if (stat.activityMean > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityMean = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12)));
|
||||
else activityMean = `${stat.activityMean}`;
|
||||
|
||||
if (stat.rolesMean <= 0) roleMean = '---';
|
||||
else if (stat.rolesMean > 54) roleMean = '54';
|
||||
else roleMean = `${stat.rolesMean}`;
|
||||
|
||||
moderationMean = `${stat.moderationMean}`;
|
||||
|
||||
if (stat.otherMean === 0) otherMean = '---';
|
||||
else otherMean = `${stat.otherMean}`;
|
||||
|
||||
if (stat.miscMean <= 0) miscMean = '---';
|
||||
else miscMean = `${stat.miscMean}`;
|
||||
|
||||
if (stat.cloudServicesMean === 0) cloudServicesMean = '---';
|
||||
else if (stat.cloudServicesMean > 10) cloudServicesMean = '10';
|
||||
else cloudServicesMean = `${stat.cloudServicesMean}`;
|
||||
|
||||
embed.setDescription(`__**Statistical Averages**__\n**CommScore™ Mean:** ${totalMean} | **CommScore™ Mode:** ${totalMode} | **CommScore™ Median:** ${totalMedian}\n\n**Activity Mean:** ${activityMean}\n**Roles Mean:** ${roleMean}\n**Moderation Mean:** ${moderationMean}\n**Cloud Services Mean:** ${cloudServicesMean}\n**Other Mean:** ${otherMean}\n**Misc Mean:** ${miscMean}`);
|
||||
embed.setAuthor(user.username, user.avatarURL);
|
||||
embed.setThumbnail(user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
split.forEach((c) => embed.addField(c.name, c.value));
|
||||
return cmdPages.push(embed);
|
||||
});
|
||||
|
||||
let name = '';
|
||||
for (const role of this.client.util.resolveMember(message.author.id, this.mainGuild).roles.map((r) => this.mainGuild.roles.get(r)).sort((a, b) => b.position - a.position)) {
|
||||
name = `Library of Code sp-us | ${role.name} - [HISTORICAL]`;
|
||||
break;
|
||||
}
|
||||
await this.client.report.createInquiry(user.id, name, 1);
|
||||
|
||||
|
||||
if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] });
|
||||
return createPaginationEmbed(message, cmdPages);
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Score_Notify extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'notify';
|
||||
this.description = 'Edits your notification preferences for Hard Inquiries.';
|
||||
this.usage = `${this.client.config.prefix}score notify <on | off>`;
|
||||
this.permissions = 0;
|
||||
this.guildOnly = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
const user = message.author;
|
||||
if (!user) return this.error(message.channel, 'Member not found.');
|
||||
const score = await this.client.db.Score.findOne({ userID: message.author.id });
|
||||
if (!score) return this.error(message.channel, 'Score not calculated yet.');
|
||||
if (!score.notify) await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { notify: false } });
|
||||
switch (args[0]) {
|
||||
case 'on':
|
||||
await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { notify: true } });
|
||||
return this.success(message.channel, 'You will now be sent notifications whenever your score is hard-pulled.');
|
||||
case 'off':
|
||||
await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { notify: false } });
|
||||
return this.success(message.channel, 'You will no longer be sent notifications when your score is hard-pulled.');
|
||||
default:
|
||||
return this.error(message.channel, 'Invalid option. Valid options are `on` and `off`.');
|
||||
}
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Score_Pref extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'pref';
|
||||
this.description = 'Locks or unlocks your Community Report.';
|
||||
this.usage = `${this.client.config.prefix}score pref <lock | unlock>`;
|
||||
this.permissions = 0;
|
||||
this.guildOnly = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!message.author) return this.error(message.channel, 'Member not found.');
|
||||
const score = await this.client.db.Score.findOne({ userID: message.author.id });
|
||||
if (!score) return this.error(message.channel, 'Score not calculated yet.');
|
||||
if (!score.locked) await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { locked: false } });
|
||||
switch (args[0]) {
|
||||
case 'lock':
|
||||
await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { locked: true } });
|
||||
return this.success(message.channel, 'Your report is now locked.');
|
||||
case 'unlock':
|
||||
await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { locked: false } });
|
||||
return this.success(message.channel, 'Your report is now unlocked.');
|
||||
default:
|
||||
return this.error(message.channel, 'Invalid input');
|
||||
}
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Setnick extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'setnick';
|
||||
this.description = 'Changes the nickname of a member';
|
||||
this.usage = 'setnick <member> [new nickname]';
|
||||
this.permissions = 2;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const member = this.client.util.resolveMember(args[0], this.mainGuild);
|
||||
if (!member) return this.error(message.channel, 'Cannot find user.');
|
||||
let nickname = args.slice(1).join(' ');
|
||||
if (args.length === 1) nickname = null;
|
||||
if (nickname?.length > 32) return this.error(message.channel, 'New nickname may not be more than 32 characters long.');
|
||||
await member.edit({ nick: nickname });
|
||||
return this.success(message.channel, `Updated the nickname of ${member.user.username}#${member.user.discriminator}.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/* eslint-disable consistent-return */
|
||||
import { Message } from 'eris';
|
||||
import type { Channel } from 'ari-client';
|
||||
import { Client, Command } from '../class';
|
||||
import { Misc } from '../pbx';
|
||||
|
||||
export default class SIP extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'sip';
|
||||
this.description = 'Dials a SIP URI.';
|
||||
this.usage = `${this.client.config.prefix}sip <uri>`;
|
||||
this.permissions = 1;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
const staff = await this.client.db.Staff.findOne({ userID: message.author.id });
|
||||
if (!staff || !staff?.extension) return this.error(message.channel, 'You must have an extension to complete this action.');
|
||||
this.success(message.channel, 'Queued call.');
|
||||
|
||||
const bridge = await this.client.pbx.ari.Bridge().create();
|
||||
let receiver: Channel = this.client.pbx.ari.Channel();
|
||||
let sender: Channel = this.client.pbx.ari.Channel();
|
||||
|
||||
try {
|
||||
sender = await this.client.pbx.ari.channels.originate({
|
||||
endpoint: `PJSIP/${staff.extension}`,
|
||||
extension: staff.extension,
|
||||
callerId: 'LOC PBX OPERATOR <operator>',
|
||||
context: 'from-internal',
|
||||
priority: 1,
|
||||
app: 'cr-zero',
|
||||
});
|
||||
} catch {
|
||||
return this.error(message.channel, 'Unable to dial your extension.');
|
||||
}
|
||||
|
||||
sender.once('StasisStart', async () => {
|
||||
await Misc.play(this.client.pbx, sender, 'sound:pls-hold-while-try');
|
||||
await sender.ring();
|
||||
|
||||
try {
|
||||
receiver = await receiver.originate({
|
||||
endpoint: `SIP/${args.join(' ')}`,
|
||||
callerId: 'LOC PBX OPERATOR <operator>',
|
||||
context: 'from-internal',
|
||||
priority: 1,
|
||||
app: 'cr-zero',
|
||||
});
|
||||
} catch {
|
||||
await Misc.play(this.client.pbx, sender, 'sound:discon-or-out-of-service');
|
||||
await sender.hangup().catch(() => {});
|
||||
return false;
|
||||
}
|
||||
|
||||
// receiver.once('StasisStart', )
|
||||
});
|
||||
|
||||
receiver.once('StasisStart', async () => {
|
||||
await sender.ringStop();
|
||||
await bridge.addChannel({ channel: [receiver.id, sender.id] });
|
||||
await bridge.play({ media: 'sound:beep' });
|
||||
});
|
||||
|
||||
receiver.once('ChannelDestroyed', async () => {
|
||||
if (!sender.connected) return;
|
||||
await Misc.play(this.client.pbx, sender, ['sound:the-number-u-dialed', 'sound:T-is-not-available', 'sound:please-try-again-later']);
|
||||
await sender.hangup().catch(() => {});
|
||||
await bridge.destroy().catch(() => {});
|
||||
});
|
||||
|
||||
receiver.once('StasisEnd', async () => {
|
||||
await sender.hangup().catch(() => {});
|
||||
await bridge.destroy().catch(() => {});
|
||||
});
|
||||
sender.once('StasisEnd', async () => {
|
||||
await receiver.hangup().catch(() => {});
|
||||
await bridge.destroy().catch(() => {});
|
||||
});
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,302 @@
|
|||
/* eslint-disable dot-notation */
|
||||
/* eslint-disable no-plusplus */
|
||||
import cheerio from 'cheerio';
|
||||
import https from 'https';
|
||||
import dns from 'dns';
|
||||
import puppeteer from 'puppeteer';
|
||||
import { createPaginationEmbed } from 'eris-pagination';
|
||||
import { promisify } from 'util';
|
||||
import axios, { AxiosError, AxiosResponse } from 'axios';
|
||||
import { Message } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
interface TLSResponse {
|
||||
status: boolean,
|
||||
message?: string,
|
||||
subject: {
|
||||
commonName: string,
|
||||
organization: string[],
|
||||
organizationalUnit: string[],
|
||||
locality: string[],
|
||||
country: string[],
|
||||
},
|
||||
issuer: {
|
||||
commonName: string,
|
||||
organization: string[],
|
||||
organizationalUnit: string[],
|
||||
locality: string[],
|
||||
country: string[],
|
||||
},
|
||||
root: {
|
||||
commonName: string,
|
||||
organization: string[],
|
||||
organizationalUnit: string[],
|
||||
locality: string[],
|
||||
country: string[],
|
||||
},
|
||||
notBefore: Date,
|
||||
notAfter: Date,
|
||||
validationType: 'DV' | 'OV' | 'EV',
|
||||
signatureAlgorithm: string,
|
||||
publicKeyAlgorithm: string,
|
||||
serialNumber: string,
|
||||
keyUsage: number[],
|
||||
keyUsageAsText: ['CRL Signing'?, 'Certificate Signing'?, 'Content Commitment'?, 'Data Encipherment'?, 'Decipher Only'?, 'Digital Signature'?, 'Encipher Only'?, 'Key Agreement'?, 'Key Encipherment'?],
|
||||
extendedKeyUsage: number[],
|
||||
extendedKeyUsageAsText: ['All/Any Usages'?, 'TLS Web Server Authentication'?, 'TLS Web Client Authentication'?, 'Code Signing'?, 'E-mail Protection (S/MIME)'?],
|
||||
san: string[],
|
||||
fingerprint: string,
|
||||
connection: {
|
||||
cipherSuite: string,
|
||||
tlsVersion: 'SSLv3' | 'TLSv1' | 'TLSv1.1' | 'TLSv1.2' | 'TLSv1.3',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export default class SiteInfo extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'site';
|
||||
this.description = 'Retrieves various information about a site. Includes web, host, and TLS information.';
|
||||
this.usage = `${this.client.config.prefix}tls <domain>\n*Only raw domain, no protocols or URLs.*`;
|
||||
this.aliases = ['ssl', 'cert', 'certinfo', 'ci', 'tls', 'si', 'siteinfo'];
|
||||
this.permissions = 0;
|
||||
this.guildOnly = true;
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
|
||||
const loading = await this.loading(message.channel, 'Loading...');
|
||||
|
||||
let author: { name?: string, icon?: string, url?: string } = {};
|
||||
let s: AxiosResponse;
|
||||
try {
|
||||
s = await axios.get(`https://${args[0]}`, {
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
loading.delete().catch(() => {});
|
||||
return this.error(message.channel, `Unable to retrieve information from site. | ${err}`);
|
||||
}
|
||||
try {
|
||||
const site = cheerio.load(s.data);
|
||||
|
||||
let iconURI: string;
|
||||
|
||||
try {
|
||||
await axios.get(`https://${args[0]}/favicon.ico`, {
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
}),
|
||||
});
|
||||
iconURI = `https://${args[0]}/favicon.ico`;
|
||||
} catch {
|
||||
const att = site('link').toArray().filter((a) => a.attribs.rel === 'icon');
|
||||
if (att?.length > 0) {
|
||||
if (att[0].attribs.href.startsWith('/') || !att[0].attribs.href.startsWith('http')) {
|
||||
iconURI = `https://${args[0]}${att[0].attribs.href}`;
|
||||
} else {
|
||||
iconURI = att[0].attribs.href;
|
||||
}
|
||||
}
|
||||
}
|
||||
author = {
|
||||
name: site('title').text(),
|
||||
icon: iconURI,
|
||||
url: `https://${args[0]}`,
|
||||
};
|
||||
} catch {
|
||||
author = {
|
||||
name: `https://${args[0]}`,
|
||||
icon: `https://${args[0]}/favicon.ico`,
|
||||
url: `https://${args[0]}`,
|
||||
};
|
||||
}
|
||||
|
||||
const embeds: RichEmbed[] = [];
|
||||
try {
|
||||
const server = await this.getServerInformation(args[0]);
|
||||
if (server) {
|
||||
const em = new RichEmbed();
|
||||
em.setTitle('Web Information');
|
||||
server.forEach((f) => em.addField(f.name, f.value, f.inline));
|
||||
embeds.push(em);
|
||||
}
|
||||
const host = await this.getHostInformation(args[0]);
|
||||
if (host) {
|
||||
const em = new RichEmbed();
|
||||
em.setTitle('Host Information');
|
||||
host.forEach((f) => em.addField(f.name, f.value, f.inline));
|
||||
embeds.push(em);
|
||||
}
|
||||
const tls = await this.getTLSInformation(args[0]);
|
||||
if (tls) {
|
||||
const em = new RichEmbed();
|
||||
em.setTitle('TLS Information');
|
||||
tls.forEach((f) => em.addField(f.name, f.value, f.inline));
|
||||
embeds.push(em);
|
||||
}
|
||||
} catch {
|
||||
loading.delete().catch(() => {});
|
||||
return this.error(message.channel, 'Unable to receive information.');
|
||||
}
|
||||
let screenshotData: string;
|
||||
try {
|
||||
const browser = await puppeteer.launch({
|
||||
ignoreHTTPSErrors: true,
|
||||
args: [
|
||||
'--ignore-certificate-errors',
|
||||
'--ignore-certificate-errors-spki-list',
|
||||
],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.goto(`https://${args[0]}`);
|
||||
screenshotData = await page.screenshot();
|
||||
browser.close();
|
||||
} catch (e) {
|
||||
this.client.util.signale.error(e);
|
||||
}
|
||||
embeds.forEach((embed) => {
|
||||
embed.setAuthor(author.name, author.icon, author.url);
|
||||
embed.setColor('#4870fe');
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
});
|
||||
|
||||
if (screenshotData) {
|
||||
if (embeds.length === 1) return message.channel.createMessage({ embed: embeds[0] }, { name: 'img.png', file: screenshotData });
|
||||
} else {
|
||||
await message.channel.createMessage('', { name: 'img.png', file: screenshotData });
|
||||
}
|
||||
loading.delete().catch(() => {});
|
||||
return await createPaginationEmbed(message, embeds, {
|
||||
cycling: true,
|
||||
extendedButtons: true,
|
||||
});
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
|
||||
public async getHostInformation(domain: string) {
|
||||
const getDNS = promisify(dns.resolve4);
|
||||
|
||||
try {
|
||||
const dnsd = await getDNS(domain);
|
||||
if (dnsd?.length < 1) return null;
|
||||
|
||||
const r = await axios.get(`http://ip-api.com/json/${dnsd[0]}`);
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.addField('IPv4 Address', dnsd[0], true);
|
||||
embed.addBlankField();
|
||||
embed.addField('Organization/Customer', r.data.org, true);
|
||||
embed.addField('Internet Service Provider', r.data.isp, true);
|
||||
embed.addField('Location', `${r.data.city}, ${r.data.regionName}, ${r.data.country}`, true);
|
||||
return embed.fields;
|
||||
} catch (err) {
|
||||
this.client.util.signale.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async getServerInformation(domain: string) {
|
||||
let r: AxiosResponse;
|
||||
try {
|
||||
r = await axios.get(`https://${domain}`, {
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
this.client.util.signale.error(err);
|
||||
return null;
|
||||
}
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.addField('Web Software', r.headers['server'] ?? 'N/A', true);
|
||||
embed.addField('Content Type', r.headers['content-type'] ? r.headers['content-type'].split(';')[0] : 'N/A', true);
|
||||
embed.addField('Content Length', r.headers['content-length'] ? this.client.util.dataConversion(r.headers['content-length']) : 'N/A', true);
|
||||
return embed.fields;
|
||||
}
|
||||
|
||||
public async getTLSInformation(domain: string) {
|
||||
https.globalAgent.options.rejectUnauthorized = false;
|
||||
|
||||
let r: AxiosResponse;
|
||||
try {
|
||||
r = await axios.get(`https://certapi.libraryofcode.org?q=${domain}`, {
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
const error = <AxiosError>err;
|
||||
return null;
|
||||
}
|
||||
const resp: TLSResponse = r.data;
|
||||
|
||||
const embed = new RichEmbed();
|
||||
|
||||
let subjectString = `**Common Name:** ${resp.subject.commonName}\n`;
|
||||
if (resp.subject.organization?.length > 0) subjectString += `**Organization:** ${resp.subject.organization[0]}\n`;
|
||||
if (resp.subject.organizationalUnit?.length > 0) subjectString += `**Organizational Unit:** ${resp.subject.organizationalUnit[0]}\n`;
|
||||
if (resp.subject.locality?.length > 0) subjectString += `**Locality:** ${resp.subject.locality[0]}\n`;
|
||||
if (resp.subject.country?.length > 0) subjectString += `**Country:** ${resp.subject.country[0]}`;
|
||||
embed.addField('Subject', subjectString, true);
|
||||
|
||||
let issuerString = `**Common Name:** ${resp.subject.commonName}\n`;
|
||||
if (resp.issuer.organization?.length > 0) issuerString += `**Organization:** ${resp.issuer.organization[0]}\n`;
|
||||
if (resp.issuer.organizationalUnit?.length > 0) issuerString += `**Organizational Unit:** ${resp.issuer.organizationalUnit[0]}\n`;
|
||||
if (resp.issuer.locality?.length > 0) issuerString += `**Locality:** ${resp.issuer.locality[0]}\n`;
|
||||
if (resp.issuer.country?.length > 0) issuerString += `**Country:** ${resp.issuer.country[0]}`;
|
||||
const rootString = `**Root Certificate:** ${resp.root.organization?.length > 0 ? resp.root.organization[0] : ''} (${resp.root.commonName ?? ''} : ${resp.root.organizationalUnit?.length > 0 ? resp.root.organizationalUnit[0] : ''})`;
|
||||
if (rootString?.length > 0) issuerString += `\n\n${rootString}`;
|
||||
embed.addField('Issuer', issuerString, true);
|
||||
|
||||
embed.addBlankField();
|
||||
|
||||
embed.addField('Issued On', new Date(resp.notBefore).toLocaleString('en-us'), true);
|
||||
embed.addField('Expiry', new Date(resp.notAfter).toLocaleString('en-us'), true);
|
||||
|
||||
embed.addBlankField();
|
||||
|
||||
let sanString = '';
|
||||
|
||||
for (let i = 0; i < resp.san.length; i++) {
|
||||
if (i >= 10) {
|
||||
sanString += '...';
|
||||
break;
|
||||
}
|
||||
sanString += `${resp.san[i]}\n`;
|
||||
}
|
||||
|
||||
embed.addField('Subject Alternative Names', sanString, true);
|
||||
|
||||
embed.addBlankField();
|
||||
|
||||
embed.addField('Key Usage', resp.keyUsageAsText.join(', '), true);
|
||||
embed.addField('Extended Key Usage', resp.extendedKeyUsageAsText.join(', '), true);
|
||||
embed.addField('Validation Type', resp.validationType, true);
|
||||
|
||||
embed.addBlankField();
|
||||
|
||||
embed.addField('Serial Number', String(resp.serialNumber), true);
|
||||
embed.addField('Fingerprint (SHA1)', resp.fingerprint, true);
|
||||
embed.addField('Signature Algorithm', resp.signatureAlgorithm, true);
|
||||
embed.addField('Public Key Algorithm', resp.publicKeyAlgorithm, true);
|
||||
|
||||
embed.addBlankField();
|
||||
|
||||
embed.addField('TLS Cipher Suite', resp.connection.cipherSuite, true);
|
||||
embed.addField('TLS Version', resp.connection.tlsVersion, true);
|
||||
|
||||
https.globalAgent.options.rejectUnauthorized = true;
|
||||
return embed.fields;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { Message, GuildTextableChannel } from 'eris';
|
||||
import moment, { unitOfTime } from 'moment';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Slowmode extends Command {
|
||||
regex: RegExp;
|
||||
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'slowmode';
|
||||
this.description = 'Set slowmode to a channel.';
|
||||
this.usage = 'slowmode <length[unit]>';
|
||||
this.permissions = 1;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
this.regex = /[a-z]+|[^a-z]+/gi;
|
||||
}
|
||||
|
||||
public async run(message: Message<GuildTextableChannel>, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
|
||||
const [length, unit] = args[0].match(this.regex);
|
||||
if (Number.isNaN(Number(length))) return this.error(message.channel, 'Could not determine the slowmode time.');
|
||||
const momentSeconds: number = Math.round(moment.duration(length, unit as unitOfTime.Base || 's').asSeconds());
|
||||
|
||||
if (momentSeconds > 21600 || momentSeconds < 0) return this.error(message.channel, 'Slowmode must be between 0 seconds and 6 hours.');
|
||||
|
||||
return message.channel.edit({ rateLimitPerUser: momentSeconds }).then((c) => message.addReaction(':success:477618704155410452'));
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
|
||||
export default class Stats extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'stats';
|
||||
this.description = 'Provides system statistics.';
|
||||
this.usage = `${this.client.config.prefix}stats`;
|
||||
this.permissions = 0;
|
||||
this.guildOnly = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message) {
|
||||
try {
|
||||
const messages = await this.client.db.Stat.findOne({ name: 'messages' });
|
||||
const commands = await this.client.db.Stat.findOne({ name: 'commands' });
|
||||
const pages = await this.client.db.Stat.findOne({ name: 'pages' });
|
||||
const requests = await this.client.db.Stat.findOne({ name: 'requests' });
|
||||
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('Statistics');
|
||||
embed.setThumbnail(this.client.user.avatarURL);
|
||||
embed.addField('Messages Seen', `${messages.value}`, true);
|
||||
embed.addField('Commands Executed', `${commands.value}`, true);
|
||||
embed.addField('HTTP Requests Served', `${requests.value}`, true);
|
||||
embed.addField('Pages Sent', `${pages.value}`, true);
|
||||
embed.addField('Jobs Processed', `${(await this.client.queue.jobCounts()).completed}`, true);
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
return message.channel.createMessage({ embed });
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import { randomBytes } from 'crypto';
|
||||
import { Message, TextChannel } from 'eris';
|
||||
import { Client, Command, LocalStorage } from '../class';
|
||||
|
||||
export default class StoreMessages extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'storemessages';
|
||||
this.description = 'Fetches 1000 messages from the specified channel and stores them in a HTML file.';
|
||||
this.usage = `${this.client.config.prefix}storemessages <channel> [member ID]`;
|
||||
this.aliases = ['sm'];
|
||||
this.permissions = 7;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const check = this.client.util.resolveGuildChannel(args[0], this.mainGuild, false);
|
||||
if (!check || check.type !== 0) return this.error(message.channel, 'The channel you specified either doesn\'t exist or isn\'t a textable guild channel.');
|
||||
const chan = <TextChannel> this.mainGuild.channels.get(check.id);
|
||||
const loadingMessage = await this.loading(message.channel, 'Fetching messages...');
|
||||
let messages = await chan.getMessages(10000);
|
||||
if (args[1]) {
|
||||
messages = messages.filter((m) => m.author.id === args[1]);
|
||||
}
|
||||
let html = `<strong><i>CLASSIFIED RESOURCE: CL-GEN/AD</i></strong><br><h3>Library of Code sp-us</h3><strong>Channel:</strong> ${chan.name} (${chan.id})<br><strong>Generated by:</strong> ${message.author.username}#${message.author.discriminator}<br><strong>Generated at:</strong> ${new Date().toLocaleString('en-us')}<br><br>`;
|
||||
for (const msg of messages) {
|
||||
html += `(<i>${new Date(msg.timestamp).toLocaleString('en-us')}</i>) [<strong>${msg.author.username}#${msg.author.discriminator} - ${msg.author.id}</strong>]: ${msg.cleanContent}<br>`;
|
||||
}
|
||||
message.delete();
|
||||
const identifier = randomBytes(20).toString('hex');
|
||||
|
||||
const comp = await LocalStorage.compress(html);
|
||||
const file = new this.client.db.File({
|
||||
name: `${chan.name}-${new Date().toLocaleString('en-us')}.html.gz`,
|
||||
identifier,
|
||||
mimeType: 'application/gzip',
|
||||
data: comp,
|
||||
downloaded: 0,
|
||||
maxDownloads: 20,
|
||||
});
|
||||
await file.save();
|
||||
loadingMessage.delete();
|
||||
this.client.getDMChannel(message.author.id).then((c) => c.createMessage(`https://cr.ins/m/${identifier}.html.gz || https://cr.ins/m/${identifier}.html?d=1`)).catch(() => this.error(message.channel, 'Could not send a DM to you.'));
|
||||
return this.success(message.channel, `Fetched messages for <#${chan.id}>. Check your DMs for link to access.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// ?e require('mongoose').connections[0].db.command({ buildInfo: 1 })
|
||||
|
||||
import { Message } from 'eris';
|
||||
import mongoose from 'mongoose';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
import { version as erisVersion } from '../../node_modules/eris/package.json';
|
||||
import { version as mongooseVersion } from '../../node_modules/mongoose/package.json';
|
||||
import { version as ariVersion } from '../../node_modules/ari-client/package.json';
|
||||
import { version as amiVersion } from '../../node_modules/asterisk-manager/package.json';
|
||||
import { version as nodeMailerVersion } from '../../node_modules/nodemailer/package.json';
|
||||
import { version as ioredisVersion } from '../../node_modules/ioredis/package.json';
|
||||
import { version as stripeVersion } from '../../node_modules/stripe/package.json';
|
||||
import { version as cronVersion } from '../../node_modules/cron/package.json';
|
||||
import { version as ttsVersion } from '../../node_modules/@google-cloud/text-to-speech/package.json';
|
||||
import { version as bullVersion } from '../../node_modules/bull/package.json';
|
||||
|
||||
export default class SysInfo extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'sysinfo';
|
||||
this.description = 'Information about the various services/system we use to run.';
|
||||
this.usage = 'sysinfo';
|
||||
this.permissions = 0;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message) {
|
||||
try {
|
||||
const embed = new RichEmbed();
|
||||
embed.setTitle('System & Service Information');
|
||||
embed.setDescription('__Format of Services__\n- {Server/Service Name} + {various server stats} | {Node Library Used for Communication w/ Server or Service}');
|
||||
const mongoBuild = await mongoose.connections[0].db.command({ buildInfo: 1 });
|
||||
const mongoDatabase: {
|
||||
collections: number,
|
||||
objects: number,
|
||||
dataSize: number,
|
||||
} = await mongoose.connections[0].db.command({ dbStats: 1 });
|
||||
const asteriskInformation = await this.client.util.pbx.ari.asterisk.getInfo();
|
||||
const redisVersion = await this.client.util.exec('redis-cli info | grep "redis_version"');
|
||||
embed.addField('Database', `- [MongoDB v${mongoBuild.version}](https://www.mongodb.com/) + Documents: ${mongoDatabase.objects} | [Mongoose ODM v${mongooseVersion}](https://github.com/Automattic/mongoose)\n- CR Local Storage w/ GZIP Compression | Internal\n- [Redis v${redisVersion.split(':')[1]}](https://redis.io/) | [IORedis v${ioredisVersion}](https://github.com/luin/ioredis)`, true);
|
||||
embed.addField('Telephony/PBX', `- [Asterisk v${asteriskInformation.system.version}](https://www.asterisk.org/) | [Asterisk ARI Node.js Client v${ariVersion}](https://github.com/asterisk/node-ari-client) & [Asterisk AMI Node.js Client v${amiVersion}](https://github.com/danjenkins/node-asterisk-ami)`, true);
|
||||
embed.addField('Email', `- [Postfix SMTP v3.1.12](http://www.postfix.org/) | [Nodemailer v${nodeMailerVersion}](https://github.com/nodemailer/nodemailer)`, true);
|
||||
embed.addField('Discord', `- N/A | [Eris v${erisVersion}](https://github.com/abalabahaha/eris)`, true);
|
||||
embed.addField('Payments', `- [Stripe API](https://stripe.com/) | [Stripe Node.js Client v${stripeVersion}](https://github.com/stripe/stripe-node)`, true);
|
||||
embed.addField('Scheduling', `- [Cron](https://systemd.io/) | [Cron Scheduling Node.js v${cronVersion}](https://github.com/node-cron/node-cron)\n- Internal Queue | [Bull v${bullVersion}](https://github.com/OptimalBits/bull) w/ IORedis`, true);
|
||||
embed.addField('Audio & Voice', `- [Google Cloud ML](https://cloud.google.com/text-to-speech) | [Google Cloud Node.js Text to Speech Synthesizer v${ttsVersion}](https://github.com/googleapis/nodejs-text-to-speech)\n - [FFMPEG](https://ffmpeg.org/) | Internal`, true);
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
message.channel.createMessage({ embed });
|
||||
} catch (err) {
|
||||
this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import { Message, TextChannel } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Train extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'train';
|
||||
this.description = 'Trains a neural network.';
|
||||
this.usage = `${this.client.config.prefix}train <channel> <message id> <1: good | 0: bad>`;
|
||||
this.permissions = 1;
|
||||
this.guildOnly = false;
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (args?.length < 3) return this.client.commands.get('help').run(message, [this.name]);
|
||||
if (args[2] !== '0' && args[2] !== '1') return this.error(message.channel, 'Result must be either 0 or 1.');
|
||||
const channel = <TextChannel> this.client.util.resolveGuildChannel(args[0], this.mainGuild);
|
||||
if (!channel) return this.error(message.channel, 'Channel could not be found.');
|
||||
if (channel.type !== 0) return this.error(message.channel, 'Invalid channel type.');
|
||||
let msg: Message;
|
||||
try {
|
||||
msg = await channel.getMessage(args[1]);
|
||||
} catch {
|
||||
return this.error(message.channel, 'Could not find message.');
|
||||
}
|
||||
if (!msg) return this.error(message.channel, 'Message could not be found.');
|
||||
|
||||
await this.client.db.NNTrainingData.updateOne({ name: 'tc' }, { $addToSet: { data: { input: this.client.util.encode(msg.content), output: { res: Number(args[2]) } } } });
|
||||
await message.delete();
|
||||
const done = await this.success(message.channel, 'Neural Network trained successfully.');
|
||||
return setTimeout(async () => {
|
||||
await done.delete();
|
||||
}, 3000);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import axios from 'axios';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class TTS extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'tts';
|
||||
this.description = 'Uses Google Text to Speech engines to synthesize input to a MP3 file. Only supports English at this time.';
|
||||
this.usage = `${this.client.config.prefix}tts <text>`;
|
||||
this.permissions = 0;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
if (args.length > 200) return this.error(message.channel, 'Cannot synthesize more than 200 characters.');
|
||||
const msg = await this.loading(message.channel, 'Synthesizing...');
|
||||
const d = await axios({
|
||||
method: 'GET',
|
||||
url: `https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=${encodeURIComponent(args.join(' '))}&tl=en`,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0',
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
msg.delete();
|
||||
return message.channel.createMessage(undefined, { name: `${uuid()}.mp3`, file: d.data });
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import { Message, User } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Unban extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'unban';
|
||||
this.description = 'Unbans a member from the guild.';
|
||||
this.usage = 'unban <user id> [reason]';
|
||||
this.permissions = 3;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
let user: User;
|
||||
try {
|
||||
user = await this.client.getRESTUser(args[0]);
|
||||
} catch {
|
||||
return this.error(message.channel, 'Could find find user.');
|
||||
}
|
||||
try {
|
||||
await this.mainGuild.getBan(args[0]);
|
||||
} catch {
|
||||
return this.error(message.channel, 'This user is not banned.');
|
||||
}
|
||||
message.delete();
|
||||
|
||||
await this.client.util.moderation.unban(user.id, message.member, args.slice(1).join(' '));
|
||||
return this.success(message.channel, `${user.username}#${user.discriminator} has been unbanned.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { Message } from 'eris';
|
||||
import { Client, Command } from '../class';
|
||||
|
||||
export default class Unmute extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'unmute';
|
||||
this.description = 'Unmutes a member.';
|
||||
this.usage = 'unmute <member> [reason]';
|
||||
this.permissions = 2;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||
const member = this.client.util.resolveMember(args[0], this.mainGuild);
|
||||
if (!member) return this.error(message.channel, 'Cannot find user.');
|
||||
|
||||
try {
|
||||
const res1 = await this.client.db.local.muted.get<boolean>(`muted-${member.id}`);
|
||||
if (!res1 || !this.mainGuild.members.get(member.id).roles.includes('478373942638149643')) return this.error(message.channel, 'This user is already unmuted.');
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
if (member && !this.client.util.moderation.checkPermissions(member, message.member)) return this.error(message.channel, 'Permission Denied.');
|
||||
message.delete();
|
||||
|
||||
await this.client.util.moderation.unmute(member.user.id, message.member, args.slice(1).join(' '));
|
||||
return this.success(message.channel, `${member.user.username}#${member.user.discriminator} has been unmuted.`);
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/* eslint-disable no-bitwise */
|
||||
import moment from 'moment';
|
||||
import { Message, Member } from 'eris';
|
||||
import { Client, Command, RichEmbed } from '../class';
|
||||
import { whois as emotes } from '../configs/emotes.json';
|
||||
|
||||
export default class Whois extends Command {
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.name = 'whois';
|
||||
this.description = 'Provides information on a member.';
|
||||
this.usage = 'whois [member]';
|
||||
this.permissions = 0;
|
||||
this.guildOnly = true;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public async run(message: Message, args: string[]) {
|
||||
try {
|
||||
let member: Member;
|
||||
if (!args[0]) member = message.member;
|
||||
else {
|
||||
member = this.client.util.resolveMember(args.join(' '), this.mainGuild);
|
||||
try {
|
||||
if (!member) member = await this.mainGuild.getRESTMember(args[0]);
|
||||
} catch {
|
||||
return this.error(message.channel, 'Member not found.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!member) {
|
||||
return this.error(message.channel, 'Member not found.');
|
||||
}
|
||||
const embed = new RichEmbed();
|
||||
embed.setThumbnail(member.avatarURL);
|
||||
const ackResolve = await this.client.db.Staff.findOne({ userID: member.id }).lean().exec();
|
||||
let title = `${member.user.username}#${member.user.discriminator}`;
|
||||
if (ackResolve?.pn?.length > 0) title += `, ${ackResolve.pn.join(', ')}`;
|
||||
embed.setAuthor(title, member.user.avatarURL);
|
||||
|
||||
let description = '';
|
||||
let titleAndDepartment = '';
|
||||
if (ackResolve?.title && ackResolve?.dept) {
|
||||
titleAndDepartment += `${emotes.titleAndDepartment} __**${ackResolve.title}**__, __${ackResolve.dept}__\n\n`;
|
||||
} else if (ackResolve?.dept) {
|
||||
titleAndDepartment += `${emotes.titleAndDepartment} __${ackResolve.dept}__\n\n`;
|
||||
}
|
||||
if (titleAndDepartment.length > 0) description += titleAndDepartment;
|
||||
if (ackResolve?.emailAddress) {
|
||||
description += `${emotes.email} ${ackResolve.emailAddress}\n`;
|
||||
}
|
||||
const pager = await this.client.db.PagerNumber.findOne({ individualAssignID: member.user.id }).lean().exec();
|
||||
if (pager?.num) {
|
||||
description += `📟 ${pager.num}\n`;
|
||||
}
|
||||
if (ackResolve?.extension) {
|
||||
description += `☎️ ${ackResolve.extension}\n`;
|
||||
}
|
||||
const memberProfile = await this.client.db.Member.findOne({ userID: member.id }).lean().exec();
|
||||
if (memberProfile?.additional?.gitlab) {
|
||||
description += `${emotes.gitlab} ${memberProfile?.additional.gitlab}\n`;
|
||||
}
|
||||
if (memberProfile?.additional?.github) {
|
||||
description += `${emotes.github} ${memberProfile?.additional.github}\n`;
|
||||
}
|
||||
if (memberProfile?.additional?.bio) {
|
||||
description += `${emotes.bio} *${memberProfile?.additional.bio}*\n`;
|
||||
}
|
||||
description += `\n<@${member.id}>`;
|
||||
embed.setDescription(description);
|
||||
|
||||
for (const role of member.roles.map((r) => this.mainGuild.roles.get(r)).sort((a, b) => b.position - a.position)) {
|
||||
if (role?.color !== 0) {
|
||||
embed.setColor(role.color);
|
||||
break;
|
||||
}
|
||||
}
|
||||
embed.addField('Status', member.status === 'dnd' ? 'Do Not Disturb' : this.client.util.capsFirstLetter(member.status) || 'Offline', true);
|
||||
embed.addField('Joined At', `${moment(new Date(member.joinedAt)).format('dddd, MMMM Do YYYY, h:mm:ss A')} ET`, true);
|
||||
embed.addField('Created At', `${moment(new Date(member.user.createdAt)).format('dddd, MMMM Do YYYY, h:mm:ss A')} ET`, true);
|
||||
const score = await this.client.db.Score.findOne({ userID: member.id }).lean().exec();
|
||||
if (score) {
|
||||
await this.client.report.createInquiry(member.id, 'Library of Code sp-us | Bureau of Community Reports', 1);
|
||||
let totalScore = '0';
|
||||
if (score.total < 200) totalScore = '---';
|
||||
else if (score.total > 800) totalScore = '800';
|
||||
else totalScore = `${score.total}`;
|
||||
embed.addField('CommScore™', totalScore, true);
|
||||
} else embed.addField('CommScore™', 'N/C', true);
|
||||
|
||||
if (member.roles.length > 0) {
|
||||
embed.addField(`Roles [${member.roles.length}]`, member.roles.map((r) => this.mainGuild.roles.get(r)).sort((a, b) => b.position - a.position).map((r) => `<@&${r.id}>`).join(', '));
|
||||
}
|
||||
|
||||
const flags: string[] = [];
|
||||
if (member.user.publicFlags) {
|
||||
if ((member.user.publicFlags & (1 << 12)) === 1 << 12) flags.push('<:System:768370601265201152>');
|
||||
if ((member.user.publicFlags & (1 << 0)) === 1 << 0) flags.push('<:DiscordStaff:768370601882025985>');
|
||||
if ((member.user.publicFlags & (1 << 1)) === 1 << 1) flags.push('<:Partnered:768370601395879936>');
|
||||
if ((member.user.publicFlags & (1 << 3)) === 1 << 3) flags.push('<:BugHunter:768370601105555467>');
|
||||
if ((member.user.publicFlags & (1 << 14)) === 1 << 14) flags.push('<:BugHunter:768370601105555467>');
|
||||
if ((member.user.publicFlags & (1 << 2)) === 1 << 2) flags.push('<:HypeSquadEvents:768370600745762846>');
|
||||
if ((member.user.publicFlags & (1 << 6)) === 1 << 6) flags.push('<:HypeSquadBravery:768370601328640011> ');
|
||||
if ((member.user.publicFlags & (1 << 7)) === 1 << 7) flags.push('<:HypeSquadBrilliance:768370600842362900>');
|
||||
if ((member.user.publicFlags & (1 << 8)) === 1 << 8) flags.push('<:HypeSquadBalance:768370599584071751> ');
|
||||
if ((member.user.publicFlags & (1 << 9)) === 1 << 9) flags.push('<:EarlySupporter:768370601873768468>');
|
||||
if ((member.user.publicFlags & (1 << 16)) === 1 << 16) flags.push('<:VerifiedBot:768370599252197396>');
|
||||
if ((member.user.publicFlags & (1 << 17)) === 1 << 17) flags.push('<:VerifiedBotDeveloper:768370601701933077>');
|
||||
}
|
||||
if (flags.length > 0) embed.addField('Flags', flags.join(' '));
|
||||
|
||||
const permissions: string[] = [];
|
||||
const serverAcknowledgements: string[] = [];
|
||||
const bit = member.permission.allow;
|
||||
if (this.mainGuild.ownerID === member.id) serverAcknowledgements.push('Server Owner');
|
||||
if (bit & 8) { permissions.push('Administrator'); serverAcknowledgements.push('Server Admin'); }
|
||||
if (bit & 32) { permissions.push('Manage Server'); serverAcknowledgements.push('Server Manager'); }
|
||||
if (bit & 16) permissions.push('Manage Channels');
|
||||
if (bit & 268435456) permissions.push('Manage Roles');
|
||||
if (bit & 8192) { permissions.push('Manage Messages'); serverAcknowledgements.push('Server Moderator'); }
|
||||
if (bit & 134217728) permissions.push('Manage Nicknames');
|
||||
if (bit & 1073741824) permissions.push('Manage Emojis');
|
||||
if (bit & 4) permissions.push('Ban Members');
|
||||
if (bit & 2) permissions.push('Kick Members');
|
||||
|
||||
|
||||
const account = await this.client.db.Member.findOne({ userID: member.id }).lean().exec();
|
||||
if (account?.additional?.langs?.length > 0) {
|
||||
const langs: string[] = [];
|
||||
for (const lang of account.additional.langs.sort((a, b) => a.localeCompare(b))) {
|
||||
switch (lang) {
|
||||
case 'asm':
|
||||
langs.push('<:AssemblyLanguage:703448714248716442> Assembly Language');
|
||||
break;
|
||||
case 'cfam':
|
||||
langs.push('<:clang:553684262193332278> C/C++');
|
||||
break;
|
||||
case 'csharp':
|
||||
langs.push('<:csharp:553684277280112660> C#');
|
||||
break;
|
||||
case 'go':
|
||||
langs.push('<:Go:703449475405971466> Go');
|
||||
break;
|
||||
case 'java':
|
||||
langs.push('<:Java:703449725181100135> Java');
|
||||
break;
|
||||
case 'js':
|
||||
langs.push('<:JavaScriptECMA:703449987916496946> JavaScript');
|
||||
break;
|
||||
case 'kt':
|
||||
langs.push('<:Kotlin:703450201838321684> Kotlin');
|
||||
break;
|
||||
case 'py':
|
||||
langs.push('<:python:553682965482176513> Python');
|
||||
break;
|
||||
case 'rb':
|
||||
langs.push('<:ruby:604812470451699712> Ruby');
|
||||
break;
|
||||
case 'rs':
|
||||
langs.push('<:Rust:703450901960196206> Rust');
|
||||
break;
|
||||
case 'swift':
|
||||
langs.push('<:Swift:703451096093294672> Swift');
|
||||
break;
|
||||
case 'ts':
|
||||
langs.push('<:TypeScript:703451285789343774> TypeScript');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
embed.addField('Known Languages', langs.join(', '));
|
||||
}
|
||||
if (account?.additional?.operatingSystems?.length > 0) {
|
||||
const operatingSystems: string[] = [];
|
||||
for (const os of account.additional.operatingSystems.sort((a, b) => a.localeCompare(b))) {
|
||||
switch (os) {
|
||||
case 'arch':
|
||||
operatingSystems.push('<:arch:707694976523304960> Arch');
|
||||
break;
|
||||
case 'deb':
|
||||
operatingSystems.push('<:debian:707695042617147589> Debian');
|
||||
break;
|
||||
case 'cent':
|
||||
operatingSystems.push('<:centos:707702165816213525> CentOS');
|
||||
break;
|
||||
case 'fedora':
|
||||
operatingSystems.push('<:fedora:707695073151680543> Fedora');
|
||||
break;
|
||||
case 'manjaro':
|
||||
operatingSystems.push('<:manjaro:707701473680556062> Manjaro');
|
||||
break;
|
||||
case 'mdarwin':
|
||||
operatingSystems.push('<:mac:707695427754917919> macOS');
|
||||
break;
|
||||
case 'redhat':
|
||||
operatingSystems.push('<:redhat:707695102159749271> RedHat Enterprise Linux');
|
||||
break;
|
||||
case 'ubuntu':
|
||||
operatingSystems.push('<:ubuntu:707695136888586300> Ubuntu');
|
||||
break;
|
||||
case 'win':
|
||||
operatingSystems.push('<:windows10:707695160259248208> Windows');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
embed.addField('Used Operating Systems', operatingSystems.join(', '));
|
||||
}
|
||||
if (permissions.length > 0) {
|
||||
embed.addField('Permissions', permissions.join(', '));
|
||||
}
|
||||
if (serverAcknowledgements.length > 0) {
|
||||
embed.addField('Acknowledgements', serverAcknowledgements[0]);
|
||||
}
|
||||
if (ackResolve?.additionalRoles?.length > 0) {
|
||||
embed.addField('Additional Acknowledgements', ackResolve.additionalRoles.join(', '));
|
||||
}
|
||||
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
|
||||
embed.setTimestamp();
|
||||
return message.channel.createMessage({ embed });
|
||||
} catch (err) {
|
||||
return this.client.util.handleError(err, message, this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"moderation": {
|
||||
"modlogs": "446080867065135115",
|
||||
"automod": ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"whois": {
|
||||
"titleAndDepartment": "<:loc:607695848612167700>",
|
||||
"email": "<:email:699786452267040878>",
|
||||
"gitlab": "<:gitlab:699788655748841492>",
|
||||
"github": "<:github:699786469404835939>",
|
||||
"bio": "<:bio:699786408193294416>"
|
||||
},
|
||||
"statusMessages": {
|
||||
"success": "<:modSuccess:578750988907970567>",
|
||||
"loading": "<a:modloading:588607353935364106>",
|
||||
"error": "<:modError:578750737920688128>"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { Emoji, Message, TextChannel } from 'eris';
|
||||
import { Client, Event } from '../class';
|
||||
|
||||
export default class CallBackHandler extends Event {
|
||||
public client: Client;
|
||||
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.event = 'messageReactionAdd';
|
||||
}
|
||||
|
||||
public async run(message: Message, emoji: Emoji, member: string) {
|
||||
try {
|
||||
if (emoji.id !== '578750988907970567' || message.channel.id !== '780513128240382002') return;
|
||||
if (member === this.client.user.id) return;
|
||||
const chan = <TextChannel> this.client.guilds.get(this.client.config.guildID).channels.get('780513128240382002');
|
||||
const msg = await chan.getMessage(message.id);
|
||||
await msg.delete();
|
||||
} catch (err) {
|
||||
this.client.util.handleError(err);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/* eslint-disable no-useless-return */
|
||||
import { Message, TextChannel, NewsChannel } from 'eris';
|
||||
import { Client, Event } from '../class';
|
||||
|
||||
export default class CommandHandler extends Event {
|
||||
public client: Client;
|
||||
|
||||
constructor(client: Client) {
|
||||
super(client);
|
||||
this.event = 'messageCreate';
|
||||
}
|
||||
|
||||
public async run(message: Message) {
|
||||
try {
|
||||
this.client.db.Stat.updateOne({ name: 'messages' }, { $inc: { value: 1 } }).exec();
|
||||
if (message.author.bot) return;
|
||||
if (message.content.indexOf(this.client.config.prefix) !== 0) return;
|
||||
const noPrefix: string[] = message.content.slice(this.client.config.prefix.length).trim().split(/ +/g);
|
||||
const resolved = await this.client.util.resolveCommand(noPrefix);
|
||||
if (!resolved) return;
|
||||
if (resolved.cmd.guildOnly && !(message.channel instanceof TextChannel || message.channel instanceof NewsChannel)) return;
|
||||
if (!resolved.cmd.enabled) { message.channel.createMessage(`***${this.client.util.emojis.ERROR} This command has been disabled***`); return; }
|
||||
if (!resolved.cmd.checkPermissions(this.client.util.resolveMember(message.author.id, this.client.guilds.get('446067825673633794')))) return;
|
||||
if ((message.channel.type === 0) && !message.channel.guild.members.get(message.author.id)) {
|
||||
message.channel.guild.members.add(await message.channel.guild.getRESTMember(message.author.id));
|
||||
}
|
||||
this.client.util.signale.log(`User '${message.author.username}#${message.author.discriminator}' ran command '${resolved.cmd.name}' in '${message.channel.id}'.`);
|
||||
await resolved.cmd.run(message, resolved.args);
|
||||
this.client.db.Stat.updateOne({ name: 'commands' }, { $inc: { value: 1 } }).exec();
|
||||
} catch (err) {
|
||||
this.client.util.handleError(err, message);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue