Main code!

This commit is contained in:
Wessel T
2019-02-02 19:36:12 +01:00
parent bd0b2055dc
commit 3b89aabb04
68 changed files with 5692 additions and 8 deletions

75
.eslintrc Normal file
View File

@@ -0,0 +1,75 @@
{
"env": {
"amd": false,
"es6": true,
"node": true,
"jest": false,
"mongo": true,
"mocha": false,
"qunit": false,
"worker": true,
"meteor": false,
"jquery": false,
"browser": false,
"jasmine": false,
"shelljs": false,
"nashorn": false,
"commonjs": true,
"atomtest": false,
"embertest": false,
"phantomjs": false,
"protractor": false,
"prototypejs": false,
"applescript": false,
"greasemonkey": false,
"webextensions": false,
"serviceworker": false,
"shared-node-browser": false
},
"parserOptions": {
"ecmaVersion": 8,
"sourceType": "module",
"ecmaFeatures": {
"restParams": true,
"impliedStrict": true,
"jsx": true,
"arrowFunctions": true,
"binaryLiterals": true,
"blockBindings": true,
"classes": true,
"defaultParams": true,
"destructuring": true,
"forOf": true,
"generators": true,
"modules": true,
"objectLiteralComputedProperties": true,
"objectLiteralDuplicateProperties": true,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
"octalLiterals": true,
"regexUFlag": true,
"regexYFlag": true,
"spread": true,
"superInFunctions": true,
"templateStrings": true,
"unicodeCodePointEscapes": true,
"globalReturn": true
}
},
"plugins": [],
"rules": {
"strict": 0,
"semi": "warn",
"indent": [
0,
2
],
"no-undef": 1,
"no-global-assign": 1,
"comma-dangle": "warn"
},
"globals": {
"_config": false,
"console": false
}
}

14
.github/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,14 @@
# Contributing
**The issue tracker is only for bug reports and enhancement suggestions. If you have a question, please ask it in the [Discord](https://discord.gg/SV7DAE9) instead of opening an issue you will get redirected there anyway.**
If you wish to contribute to Wump, feel free to fork the repository and submit a pull request.
[ESLint](https://eslint.org/) is used to correct most typo's you make, so it would be helpful if you added [ESLint](https://eslint.org/) to your editor of choice)
## Setup
To get ready to work on the codebase, please do the following:
1. Fork & clone the repository, and make sure you're on the **master** branch
2. Run `yarn --dev` or `npm install --dev`
4. Code your heart out and test using `yarn test` or `npm run test`!
6. [Submit a pull request](https://github.com/PassTheWessel/wump/compare)

23
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,23 @@
---
name: Bug Report
description: Report incorrect or unexpected behaviour
---
**Please describe the problem you are having in as much detail as possible:**
**Include a reproducible code sample here, if possible:**
```js
// Place your code here
```
**Further details:**
- Node.js version:
- Operating system:
<!--
If this applies to you, please check the respective checkbox: [ ] becomes [x].
-->
- [ ] I have also tested the issue on latest master, commit hash:

View File

@@ -0,0 +1,18 @@
---
name: Feature request
about: Request a feature
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the ideal solution**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

10
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,10 @@
**Please describe the changes this PR makes and why it should be merged:**
**Status**
- [ ] Code changes have been tested and there aren't any typos in it
**Semantic versioning classification:**
- [ ] This PR changes the library's interface (methods or parameters added)
- [ ] This PR includes breaking changes (methods removed or renamed, parameters moved or removed)
- [ ] This PR **only** includes non-code changes, like changes to documentation, README, etc.

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
web/
src/tmp
node_modules/
src/application.yml
.vscode/

17
LICENSE
View File

@@ -1,7 +1,8 @@
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
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.
@@ -631,8 +632,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -645,14 +646,14 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
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 the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
Copyright (C) 2019-present Wessel Tip
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -664,11 +665,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

15
README.md Normal file
View File

@@ -0,0 +1,15 @@
<div align="center">
<br />
<p>
<a href="https://github.com/PassTheWessel/wump">Wump</a>
</p>
<br />
<p>
<a href="https://discord.gg/SV7DAE9"><img src="https://discordapp.com/api/guilds/107131083958538240/embed.png" alt="Discord" /></a>
<a href="https://www.patreon.com/wessel"><img src="https://img.shields.io/badge/donate-patreon-F96854.svg" alt="Patreon" /></a>
</p>
</div>
> A multi-platform bot written fully in Node.js
You may use this project, but only **if** you follow the [licensing](./LICENSE)

29
package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "wump",
"version": "1.2.0",
"displayName": "Wump",
"description": "A multi-platform bot written in Node.js",
"author": "Wessel \"wesselgame\" T <discord@go2it.eu>",
"contributors": [],
"maintainers": [
"Wessel \"wesselgame\" T <discord@go2it.eu >"
],
"license": "GPL-3.0",
"dependencies": {
"eris": "^0.9.0",
"js-yaml": "^3.12.0",
"larg": "^0.0.1",
"mathjs": "^5.1.2",
"moment": "^2.22.2",
"mongoose": "^5.2.17",
"nabbit": "^0.0.2",
"table": "^5.1.0",
"vm2": "^3.6.3",
"wumpfetch": "^0.0.5"
},
"devDependencies": {
"eslint": "^5.6.0"
},
"main": "src/main.js",
"repository": "https://github.com/PassTheWessel/wump"
}

66
src/_application.yml Normal file
View File

@@ -0,0 +1,66 @@
#-----------------[ Wump config ]-----------------#
# This is the configuration file of "Wump", all #
# your sensitive information is stored inside of #
# this file. Do **not** give this file or it's #
# contents to **anyone**, they can do a lot of #
# bad things with your tokens. Don't forget to #
# follow the licensing. - Wessel #
#-------------[ Configurable values ]-------------#
# db - <String>: The db URI (Mongo) #
# debug - <Number[0-3]>: The logging leven #
# nightly - <Boolean>: Run inside of dev mode #
# discord: #
# enabled - <Boolean>: Enable/disable client #
# op - <Array>: The bot's OwnerIDs #
# token - <String>: The bot's tokens #
# prefix - <String>: The command prefix #
# events - Each<Boolean>: Enable/disable evts #
# playing - Each<any>: The bot's status #
# commands - Each<Boolean>: Enable/disable cmds #
#-------------------------------------------------#
db : 'mongodb://localhost:27017/Wump'
debug : 3
nightly: true
discord :
enabled: true
token : '<--snip-->'
prefix : '!'
locale : 'en_us'
op :
- '107130754189766656'
playing :
url : 'https://twitch.tv/wumpusapp'
name : 'type !help'
type : 1 # 0 - Playing / 1 - Streaming / 2 - Listening / 3 - Watching
status: 'online' # online / idle / dnd / invisible
events :
ready : true
disconnect : true
messagecreate: true
messagedelete: true
commands :
# Category : UTILITY
ping : true
math : true
snipe : true
# Category : INFORMATION
stats : true
commands : true
# Category : IMAGE
cat : true
dog : true
fox : true
duck : true
birb : true
lizard : true
penguin : true
# Category : DEVELOPER
eval : true
echo : true
api :
lol: '<--snip-->'

View File

@@ -0,0 +1,28 @@
error : 0xff0000 # Red
cooldown: 0x7289DA # Blurple
util :
ping :
0 : 0xffa500 # Orange
1 : 0x00ff00 # Green
2 : 0xffa500 # Orange
3 : 0xff0000 # Red
snipe: 0x23272A # Not quite black
info :
stats : 0x02C2F33 # Dark but not black
commands: 0x02C2F33 # Dark but not black
image :
cat : 0x7289DA # Blurple
dog : 0x7289DA # Blurple
fox : 0x7289DA # Blurple
birb : 0x7289DA # Blurple
duck : 0x7289DA # Blurple
lizard : 0x7289DA # Blurple
penguin: 0x7289DA # Blurple
developer :
eval :
success: 0x00ff00 # Green
failure: 0xff0000 # Red

View File

@@ -0,0 +1,55 @@
cooldown : <:sad:500719642403405824> # On cooldown
owner_only: <:sad:500719642403405824> # Only owners
guild_only: <:sad:500719642403405824> # Only guilds
util :
ping:
0 : <:bored:500719642164199425> # Pinging
1 : <:thumbsup:501844577725054976> # Heartbeat
2 : <:blueberry:506201160382939141> # Round-trip
3 : <:WumpLove:506200781549076482> # Health
math:
0 : <:blueberry:506201160382939141> # Calculating
1 : <a:ablobdevil:494927811451027458> # Evaluation
2 : <a:agoogleoctopus:494927680911704067> # Invalid equation
snipe : <:snipe:508653323788877825> # No snipes found
info:
commands :
0 : <:blueberry:506201160382939141> # Guild prefix
1 : <:spirit:332131321759399937> # More info
2 : <:WumpLove:506200781549076482> # Links
statistics :
0 : <:ok_hand:497396122416054287> # Fetching
1 : <:blueberry:506201160382939141> # Operating System
2 : <:happy:500719642231177220> # Process hash
3 : <:WumpLove:506200781549076482> # Load average
4 : <a:ablobangel:494926715592900608> # OS uptime
5 : <a:agoogleoctopus:494927680911704067> # Uptime
6 : <:monkey:369880931630317568> # Memory usage
7 : <:spirit:332131321759399937> # User agent
8 : <:dev_server:495573905092902942> # Shards
9 : <:beetle_tank:445274774579445771> # Cache
10 : <:srv_server:495680009197977617> # Channels
11 : <:srv_tags:496035430052134912> # Guilds
12 : <:srv_user:495917052784148480> # Users
image :
cat :
0 : <:blueberry:506201160382939141> # Fetching
1 : <:spirit:332131321759399937> # Cat fact
dog :
0 : <:blueberry:506201160382939141> # Fetching
1 : <:spirit:332131321759399937> # dog fact
fox : <:blueberry:506201160382939141> # Fetching
birb : <:blueberry:506201160382939141> # Fetching
duck : <:blueberry:506201160382939141> # Fetching
lizard : <:blueberry:506201160382939141> # Fetching
penguin: <:blueberry:506201160382939141> # Fetching
developer:
echo : <a:voting:501106582915121154> # Invalid message
eval :
0 : <a:voting:501106582915121154> # Invalid snippet
1 : <a:agoogleoctopus:494927680911704067> # Evaluating
2 : <a:voting:501106582915121154> # Result

View File

@@ -0,0 +1,79 @@
translation :
code : 'en_us' # Language code
flag : '🇺🇸' # Language flag
full : 'English (United States)' # Full language name in english
orig : 'English (United States)' # Full language name in your language
translator : # Your information (can leave fields empty)
email : 'discord@go2it.eu'
website : 'https://wessel.meek.moe'
discord :
name : 'Wesselgame'
userid : '107130754189766656'
discrim: '0498'
error :
- '$[author:mention] **>** An error occured while executing this command'
- 'If this problem keeps occurring, consider joining ***https://discord.gg/SV7DAE9*** and posting your problem there'
- '```ini'
- '[ Error Information ]'
- 'Code = "$[err:code]"'
- 'Message = "$[err:message]"'
- '```'
cooldown: '$[emoji#0] $[author:mention] **>** This command is on cooldown for **$[cooldown:left]**'
util :
ping :
busy : '$[emoji#0] Pinging...'
health :
- 'Unknown'
- 'Good'
- 'Okay'
- 'Bad'
result :
- '$[emoji#2] **Round-Trip**: $[roundtrip]ms'
- '$[emoji#1] **Heartbeat**: $[latency]ms'
- '$[emoji#3] **Health**: $[health]'
math :
busy : '$[emoji#0] Calculating, this may take some time...'
blocked: '<blocked> Method "$[math:function]" has been disabled for security reasons <blocked>'
snipe :
fail : '$[emoji#0] No snipeable messages found'
footer : 'Sniped by $[author:tag] ($[author:id])'
info :
stats :
fetching: '$[emoji#0] Fetching statistics, this may take some time...'
commands:
multi :
- '$[emoji#0] The prefix for **$[guild:name]** is `$[guild:prefix]`'
- '$[emoji#1] Type `$[guild:prefix]command <command:str>` for more information'
- '$[emoji#2] **Links**: [`Invite`]($[bot:invite]) **|** [`Support`](https://discord.gg/SV7DAE9) **|** [`GitHub`](https://github.com/PassTheWessel/wump) **|** [`Patreon`](https://patreon.com/wessel)'
single:
- '**Name**: $[command:name]'
- '**Syntax**: $[command:syntax]'
- '**Aliases**: $[command:aliases]'
- '**Cooldown**: $[command:cooldown#formatted]'
- '**Arguments**: $[command:arguments]'
fields:
- 'Information'
- 'Image'
- 'Utility'
- 'Developer'
image:
fetching : '$[emoji#0] Fetching a random image, this may take some time...'
failed_cache: 'Click here if the image didn''t load properly'
developer:
echo : '$[emoji#0] Invalid content to echo'
eval :
args : '$[emoji#0] Invalid snippet of code to evaluate'
busy: '$[emoji#1] Evaluating, this may take some time...'
types:
- 'succeed'
- 'failed'
result:
- '$[emoji#2] **Result** (`$[result:type]`):'
- '```js'
- '$[result:message]'
- '```'

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -0,0 +1,32 @@
const { DiscordCommand } = require('../../../core');
module.exports = class Eval extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'echo',
syntax : 'echo <...message:str>',
aliases : [],
argument : [ '<...message:str>' ],
description : 'Evaluate a snippet',
hidden : false,
enabled : true,
cooldown : 0,
category : 'Developer',
ownerOnly : true,
guildOnly : false,
permissions : [ 'embedLinks' ]
});
}
async execute(msg, args) {
if (args.length <= 0) return msg.channel.createMessage(this.localize(msg.author.locale['developer']['echo']));
msg.channel.createMessage(args.join(' '));
}
localize(msg) {
if (!msg) return '';
return msg.replace(/\$\[emoji#0]/g, this.bot.emote('developer', 'echo'));
}
};

View File

@@ -0,0 +1,68 @@
const { DiscordCommand } = require('../../../core');
const { inspect } = require('util');
module.exports = class Eval extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'eval',
syntax : 'eval <...code:str>',
aliases : [],
argument : [],
description: 'Evaluate a snippet',
hidden : false,
enabled : true,
cooldown : 0,
category : 'Developer',
ownerOnly : true,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
}
async execute(msg, args) {
let result;
let errored = false;
if (args.length <= 0) return msg.channel.createMessage(this.localize(msg.author.locale['developer']['eval']['args']));
const message = await msg.channel.createMessage(this.localize(msg.author.locale['developer']['eval']['busy']));
try { result = eval(args.join(' ')); }
catch (ex) { result = ex; errored = true; }
finally {
result = this.sanitize(String(inspect(result)));
message.edit({
content: '',
embed: {
color : errored ? this.bot.col['developer']['eval']['failure'] : this.bot.col['developer']['eval']['success'],
description: this.localize(msg.author.locale['developer']['eval']['result'].join('\n'), { resultType: errored ? msg.author.locale['developer']['eval']['types'][1] : msg.author.locale['developer']['eval']['types'][0], resultMessage: this.bot.util.shorten(result, 1950) || '{}' })
}
});
};
}
sanitize(msg) {
if (!msg) return undefined;
for(let _ in this.bot.conf['api']) msg = msg.replace(new RegExp(this.bot.conf['api'][_], 'gi'), '<--snip-->');
return msg
.replace(new RegExp(this.bot.token, 'gi'), '<--snip-->')
.replace(new RegExp(this.bot.conf['discord']['token'], 'gi'), '<--snip-->');
}
localize(msg, extData) {
if (!msg) return '';
if (extData && extData.resultType) msg = msg.replace(/\$\[result:type]/g, extData.resultType);
if (extData && extData.resultMessage) msg = msg.replace(/\$\[result:message]/g, extData.resultMessage);
return msg
.replace(/\$\[emoji#0]/g, this.bot.emote('developer', 'eval', '0'))
.replace(/\$\[emoji#1]/g, this.bot.emote('developer', 'eval', '1'))
.replace(/\$\[emoji#2]/g, this.bot.emote('developer', 'eval', '2'));
}
};

View File

@@ -0,0 +1,45 @@
const { DiscordCommand } = require('../../../core');
const w = require('wumpfetch');
module.exports = class Birb extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'birb',
syntax : 'birb',
aliases : [ 'bird' ],
argument : [],
description: 'Get a random birb',
hidden : false,
enabled : true,
cooldown : 2500,
category : 'Image',
ownerOnly : false,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
}
async execute(msg) {
const message = await msg.channel.createMessage(this.localize(msg.author.locale['image']['fetching']));
let img = await w('https://some-random-api.ml/birbimg', { headers: { 'User-Agent': this.bot.ua } }).send();
img = img.json();
msg.channel.createMessage({
embed: {
color : this.bot.col['image']['birb'],
image : { url: img.link ? img.link : '' },
description: `*[\`${msg.author.locale['image']['failed_cache']}\`](${img && img.link ? img.link : 'https://www.google.com/'})*`
}
});
message.delete();
}
localize(msg) {
if (!msg) return '';
return msg.replace(/\$\[emoji#0]/g, this.bot.emote('image', 'birb'));
}
};

View File

@@ -0,0 +1,47 @@
const { DiscordCommand } = require('../../../core');
const w = require('wumpfetch');
module.exports = class Cat extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'cat',
syntax : 'cat',
aliases : [ 'kitten' ],
argument : [],
description: 'Get a random cat',
hidden : false,
enabled : true,
cooldown : 2500,
category : 'Image',
ownerOnly : false,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
}
async execute(msg) {
const message = await msg.channel.createMessage(this.localize(msg.author.locale['image']['fetching']));
let img = await w('https://aws.random.cat/meow', { headers: { 'User-Agent': this.bot.ua } }).send();
let res = await w('https://catfact.ninja/fact?max_length=1500', { headers: { 'User-Agent': this.bot.ua } }).send();
img = img.json();
res = res.json();
msg.channel.createMessage({
embed: {
color : this.bot.col['image']['cat'],
image : { url: img.file ? img.file : '' },
description: `${this.bot.emote('image', 'cat', '1')} *Random Fact* **>** ${res.fact}\n\n*[\`${msg.author.locale['image']['failed_cache']}\`](${img && img.file ? img.file : 'https://www.google.com/'})*`
}
});
message.delete();
}
localize(msg) {
if (!msg) return '';
return msg.replace(/\$\[emoji#0]/g, this.bot.emote('image', 'cat', '0'));
}
};

View File

@@ -0,0 +1,47 @@
const { DiscordCommand } = require('../../../core');
const w = require('wumpfetch');
module.exports = class Dog extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'dog',
syntax : 'dog',
aliases : [ 'doggo' ],
argument : [],
description: 'Get a random dog',
hidden : false,
enabled : true,
cooldown : 5000,
category : 'Image',
ownerOnly : false,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
}
async execute(msg) {
const message = await msg.channel.createMessage(this.localize(msg.author.locale['image']['fetching']));
let img = await w('https://dog.ceo/api/breeds/image/random', { headers: { 'User-Agent': this.bot.ua } }).send();
let res = await w('https://some-random-api.ml/dogfact', { headers: { 'User-Agent': this.bot.ua } }).send();
img = img.json();
res = res.json();
msg.channel.createMessage({
embed: {
color : this.bot.col['image']['dog'],
image : { url: img.message ? img.message : '' },
description: `${this.bot.emote('image', 'dog', '1')} *Random Fact* **>** ${res.fact.slice(0, 1990)}\n\n*[\`${msg.author.locale['image']['failed_cache']}\`](${img && img.message ? img.message : 'https://www.google.com/'})*`
}
});
message.delete();
}
localize(msg) {
if (!msg) return '';
return msg.replace(/\$\[emoji#0]/g, this.bot.emote('image', 'dog', '0'));
}
};

View File

@@ -0,0 +1,45 @@
const { DiscordCommand } = require('../../../core');
const w = require('wumpfetch');
module.exports = class Duck extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'duck',
syntax : 'duck',
aliases : [ 'quack' ],
argument : [],
description: 'Get a random duck',
hidden : false,
enabled : true,
cooldown : 5000,
category : 'Image',
ownerOnly : false,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
}
async execute(msg) {
const message = await msg.channel.createMessage(this.localize(msg.author.locale['image']['fetching']));
let img = await w('https://random-d.uk/api/v1/random', { headers: { 'User-Agent': this.bot.ua } }).send();
img = img.json();
msg.channel.createMessage({
embed: {
color : this.bot.col['image']['duck'],
image : { url: img.url ? img.url : '' },
description: `*[\`${msg.author.locale['image']['failed_cache']}\`](${img && img.url ? img.url : 'https://www.google.com/'})*`
}
});
message.delete();
}
localize(msg) {
if (!msg) return '';
return msg.replace(/\$\[emoji#0]/g, this.bot.emote('image', 'duck'));
}
};

View File

@@ -0,0 +1,44 @@
const { DiscordCommand } = require('../../../core');
const w = require('wumpfetch');
module.exports = class Cat extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'fox',
syntax : 'fox',
aliases : [ 'fox' ],
argument : [],
description: 'Get a random fox',
hidden : false,
enabled : true,
cooldown : 2500,
category : 'Image',
ownerOnly : false,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
}
async execute(msg) {
const message = await msg.channel.createMessage(this.localize(msg.author.locale['image']['fetching']));
let img = await w('https://randomfox.ca/floof/', { headers: { 'User-Agent': this.bot.ua } }).send();
img = img.json();
msg.channel.createMessage({
embed: {
color : this.bot.col['image']['fox'],
image : { url: img.image ? img.image : '' },
description: `*[\`${msg.author.locale['image']['failed_cache']}\`](${img && img.image ? img.image : 'https://www.google.com/'})*`
}
});
message.delete();
}
localize(msg) {
if (!msg) return '';
return msg.replace(/\$\[emoji#0]/g, this.bot.emote('image', 'fox'));
}
};

View File

@@ -0,0 +1,45 @@
const { DiscordCommand } = require('../../../core');
const w = require('wumpfetch');
module.exports = class Cat extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'lizard',
syntax : 'lizard',
aliases : [],
argument : [],
description: 'Get a random lizard',
hidden : false,
enabled : true,
cooldown : 2500,
category : 'Image',
ownerOnly : false,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
}
async execute(msg) {
const message = await msg.channel.createMessage(this.localize(msg.author.locale['image']['fetching']));
let img = await w('https://nekos.life/api/v2/img/lizard', { headers: { 'User-Agent': this.bot.ua } }).send();
img = img.json();
msg.channel.createMessage({
embed: {
color : this.bot.col['image']['lizard'],
image : { url: img.url ? img.url : '' },
description: `*[\`${msg.author.locale['image']['failed_cache']}\`](${img && img.url ? img.url : 'https://www.google.com/'})*`
}
});
message.delete();
}
localize(msg) {
if (!msg) return '';
return msg.replace(/\$\[emoji#0]/g, this.bot.emote('image', 'lizard'));
}
};

View File

@@ -0,0 +1,45 @@
const { DiscordCommand } = require('../../../core');
const w = require('wumpfetch');
module.exports = class Cat extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'penguin',
syntax : 'penguin',
aliases : [ 'pengu', 'waddle' ],
argument : [],
description: 'Get a random pengu',
hidden : false,
enabled : true,
cooldown : 2500,
category : 'Image',
ownerOnly : false,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
}
async execute(msg) {
const message = await msg.channel.createMessage(this.localize(msg.author.locale['image']['fetching']));
let img = await w('https://animals.anidiots.guide/penguin', { headers: { 'User-Agent': this.bot.ua } }).send();
img = img.json();
msg.channel.createMessage({
embed: {
color : this.bot.col['image']['penguin'],
image : { url: img.link ? img.link : '' },
description: `*[\`${msg.author.locale['image']['failed_cache']}\`](${img && img.link ? img.link : 'https://www.google.com/'})*`
}
});
message.delete();
}
localize(msg) {
if (!msg) return '';
return msg
.replace(/\$\[emoji#0]/g, this.bot.emote('image', 'penguin'));
}
};

View File

@@ -0,0 +1,93 @@
const { DiscordCommand } = require('../../../core');
const moment = require('moment'); require('../../../util/moment/format');
const { readFileSync } = require('fs');
module.exports = class Commands extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'commands',
syntax : 'commands [command:str]',
aliases : [ 'command', 'cmds', 'cmd', 'help' ],
argument : [ '[command:str]' ],
description: 'Lists all commands',
hidden : false,
enabled : true,
cooldown : 1000,
category : 'Information',
ownerOnly : false,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
try { this.thumb = readFileSync('./assets/img/thumb/commands.png'); }
catch(ex) { this.thumb = undefined; }
}
async execute(msg, args) {
let cmd = this.bot.cmds.filter((v) => v.extData.name.toLowerCase() === args.join(' ').toLowerCase() || v.extData.aliases.includes(args.join(' ').toLowerCase()));
if (cmd.length >= 1) {
cmd = cmd[0].extData;
msg.channel.createMessage({
embed: {
description: this.localize(msg.author.locale['info']['commands']['single'].join('\n'), { msg: msg, cmd: cmd })
}
});
} else {
msg.channel.createMessage({
embed: {
color : this.bot.col['info']['commands'],
thumbnail : { url: 'attachment://thumb.png' },
description: this.localize(msg.author.locale['info']['commands']['multi'].join('\n'), { msg: msg }),
fields : [
{
name : msg.author.locale['info']['commands']['fields'][0],
value : this.bot.cmds.filter((v) => v.extData.category.toLowerCase() === 'information' && !v.extData.hidden).map((v) => `**•** [\`${msg.prefix}${v.extData.name}\`](https://discordapp.com/channels/${msg.channel.guild.id}/${msg.channel.id}/${msg.id}) ${v.extData.description ? `**-** ${v.extData.description}` : ''}`).join('\n'),
inline: true
},
{
name : msg.author.locale['info']['commands']['fields'][1],
value : this.bot.cmds.filter((v) => v.extData.category.toLowerCase() === 'image' && !v.extData.hidden).map((v) => `**•** [\`${msg.prefix}${v.extData.name}\`](https://discordapp.com/channels/${msg.channel.guild.id}/${msg.channel.id}/${msg.id}) ${v.extData.description ? `**-** ${v.extData.description}` : ''}`).join('\n'),
inline: true
},
{
name : msg.author.locale['info']['commands']['fields'][2],
value : this.bot.cmds.filter((v) => v.extData.category.toLowerCase() === 'utility' && !v.extData.hidden).map((v) => `**•** [\`${msg.prefix}${v.extData.name}\`](https://discordapp.com/channels/${msg.channel.guild.id}/${msg.channel.id}/${msg.id}) ${v.extData.description ? `**-** ${v.extData.description}` : ''}`).join('\n'),
inline: true
},
{
name : msg.author.locale['info']['commands']['fields'][3],
value : this.bot.cmds.filter((v) => v.extData.category.toLowerCase() === 'developer' && !v.extData.hidden).map((v) => `**•** [\`${msg.prefix}${v.extData.name}\`](https://discordapp.com/channels/${msg.channel.guild.id}/${msg.channel.id}/${msg.id}) ${v.extData.description ? `**-** ${v.extData.description}` : ''}`).join('\n'),
inline: true
}
]
}
},
this.thumb ? { file: this.thumb, name: 'thumb.png' } : undefined);
}
}
localize(msg, extData) {
if (!msg) return '';
if (extData && extData.msg) msg = msg
.replace(/\$\[guild:name]/g, extData.msg.channel.guild.name)
.replace(/\$\[guild:prefix]/g, extData.msg.prefix)
.replace(/\$\[bot:invite]/g, this.bot.gatherInvite(8));
if (extData && extData.cmd) msg = msg
.replace(/\$\[command:name]/g, extData.cmd.name)
.replace(/\$\[command:syntax]/g, `${extData.msg.prefix}${extData.cmd.syntax}`)
.replace(/\$\[command:aliases]/g, extData.cmd.aliases.map((v) => `\`${v}\``).join(', ') || '`n/a`')
.replace(/\$\[command:arguments]/g, extData.cmd.argument.map((v) => `\`${v}\``).join(', ') || '`n/a`')
.replace(/\$\[command:cooldown#formatted]/g, moment.duration(extData.cmd.cooldown ? extData.cmd.cooldown : 0).format('YYYY[y] MM[M] DD[d] HH[h] mm[m] ss[s]'));
return msg
.replace(/\$\[emoji#0]/g, this.bot.emote('info', 'commands', '0'))
.replace(/\$\[emoji#1]/g, this.bot.emote('info', 'commands', '1'))
.replace(/\$\[emoji#2]/g, this.bot.emote('info', 'commands', '2'));
}
};

View File

@@ -0,0 +1,122 @@
const { DiscordCommand } = require('../../../core');
const os = require('os');
const larg = require('larg');
const moment = require('moment'); require('../../../util/moment/format');
const { readFileSync } = require('fs');
module.exports = class Stats extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'stats',
syntax : 'stats [--{no-deps|no-bot|no-sys|no-cpu|no-thumb}]',
aliases : [ 'statistics' ],
argument : [ '[--{no-deps|no-bot|no-sys|no-cpu|no-thumb}]' ],
description: 'Some small statistics',
hidden : false,
enabled : true,
cooldown : 5000,
category : 'Information',
ownerOnly : false,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
try { this.thumb = readFileSync('./assets/img/thumb/stats.png'); }
catch(ex) { this.thumb = undefined; }
}
async execute( msg, args ) {
args = larg(args);
const message = await msg.channel.createMessage(this.localize(msg.author.locale['info']['stats']['fetching']));
const cpus = os.cpus();
let platform = {
'arch' : os.arch() ? os.arch() : '??x',
'release' : os.release() ? os.release() : '?.?.?',
'platform': os.platform()
};
switch (platform.platform) {
case 'aix' : platform.platform = 'Linux'; break;
case 'sunos' : platform.platform = 'Linux'; break;
case 'win32' : platform.platform = 'Windows'; break;
case 'linux' : platform.platform = 'Linux'; break;
case 'darwin' : platform.platform = 'Macintosh'; break;
case 'freebsd': platform.platform = 'Linux'; break;
case 'openbsd': platform.platform = 'Linux'; break;
case 'android': platform.platform = 'Android'; break;
default : platform.platform = 'Unknown';
}
const fields = [];
if (!args['no-bot']) {
fields.push({
name : ' Bot information',
value :
[
`${this.bot.emote('info', 'statistics', '7')} **User Agent**: ${this.bot.ua ? this.bot.ua : 'Unknown'}`,
`${this.bot.emote('info', 'statistics', '8')} **Shards \`[Current / Total]\`**: ${msg.channel.guild.shard.id} **/** ${this.bot.shards.size}`,
`${this.bot.emote('info', 'statistics', '9')} **Cache \`[Users / Guilds]\`**: ${this.bot.cache.get('users').length || 0} **/** ${this.bot.cache.get('guilds').length || 0}`,
`${this.bot.emote('info', 'statistics', '10')} **Channels**: ${Object.keys(this.bot.channelGuildMap).length}`,
`${this.bot.emote('info', 'statistics', '11')} **Guilds**: ${this.bot.guilds.size}`,
`${this.bot.emote('info', 'statistics', '12')} **Users**: ${this.bot.users.size}`
].join('\n'),
inline: true
});
}
if (!args['no-sys']) {
fields.push({
name : ' System and process information',
value :
[
`${this.bot.emote('info', 'statistics', '1')} **Operating System**: ${platform.platform} ${platform.release} **(**${platform.arch}**)**`,
`${this.bot.emote('info', 'statistics', '2')} **Process Hash**: ${process.hash}`,
`${this.bot.emote('info', 'statistics', '3')} **Load Average**: ${os.loadavg().join(' **/** ')}`,
`${this.bot.emote('info', 'statistics', '4')} **OS Uptime**: ${moment.duration(os.uptime() * 1000).format('YYYY[y] MM[M] DD[d] HH[h] mm[m] ss[s]')}`,
`${this.bot.emote('info', 'statistics', '5')} **Uptime**: ${moment.duration(this.bot.uptime).format('YYYY[y] MM[M] DD[d] HH[h] mm[m] ss[s]')}`,
`${this.bot.emote('info', 'statistics', '6')} **Memory**: ${this.formatBytes(process.memoryUsage().heapUsed)} **/** ${this.formatBytes(os.totalmem())} **(**${((process.memoryUsage().heapUsed / os.totalmem()) * 100).toFixed(2)}%**)**`
].join('\n'),
inline: true
});
}
if (!args['no-deps']) {
fields.push({
name : ' Runtime & Dependencies',
value : `**Node.js**: ${process.version}\n\n${Array.list(Object.keys(this.bot.pkg.dependencies).map((v, _) => `\`${v}\``))}`,
inline: true
});
}
if (!args['no-cpu']) {
fields.push({
name : ' CPU information',
value : `\`\`\`ini\navailable=${cpus.length}\nendianness="${os.endianness()}"\n; List of all (available) CPU's\n${cpus.map((v, _) => `[${_ + 1}] ${v.model}`).join('\n')}\`\`\``,
inline: true
});
}
msg.channel.createMessage({
content: '',
embed: {
color : this.bot.col['info']['stats'],
fields : fields,
thumbnail: { url: !args['no-thumb'] ? 'attachment://thumb.png' : '' }
}
},
this.thumb && !args['no-thumb'] ? { file: this.thumb, name: 'thumb.png' } : '');
message.delete();
}
formatBytes(b) {
if(b < 1073741824) return(b / 1048576).toFixed(2) + ' MB';
else return(b / 1073741824).toFixed(2) + ' GB';
}
localize(msg) {
if (!msg) return '';
return msg.replace(/\$\[emoji#0]/g, this.bot.emote('info', 'statistics', '0'));
}
};

View File

@@ -0,0 +1,52 @@
const { DiscordCommand } = require('../../../core');
const { NodeVM } = require('vm2');
const { eval: mEval, config } = require('mathjs');
module.exports = class Math extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'math',
syntax : 'math <...equation:str>',
aliases : [],
argument : [ '<...equation:str>' ],
description : 'Evaluate an equation',
hidden : false,
enabled : true,
cooldown : 5000,
category : 'Utility',
ownerOnly : false,
guildOnly : false,
permissions : []
});
this.regex = /eval|import|parse|simplify|derivative|createUnit/gi;
config({ number: 'BigNumber', precision: 64 });
}
async execute(msg, args) {
let sandbox = null;
let message = null;
let blocked = undefined;
let origMsg = await msg.channel.createMessage(this.localize(msg.author.locale['util']['math']['busy']));
let equation = 0;
try {
equation = (args[0] ? args.join(' ') : '2+2-1');
blocked = this.regex.exec(equation);
if (blocked && blocked[0] && !this.bot.op(msg.author.id)) throw msg.author.locale['util']['math']['blocked'].replace(/\$\[math:function]/g, blocked[0]);
const vm = new NodeVM({ timeout: 10000, sandbox: { mEval: mEval }, require: { external: true } });
sandbox = vm.run('module.exports = function(equation) { return mEval(`${equation}`) }');
message = `${this.bot.util.shorten(`${this.bot.emote('util', 'math', '1' )} ${msg.author.mention} **>** \`${this.bot.util.escapeMarkdown(equation.replace(/ /gi, ''), false, true)}\` = \`${sandbox(equation) || 0}\``, 2000)}`;
} catch (ex) {
message = `${this.bot.util.shorten(`${this.bot.emote( 'util', 'math', '2' )} ${msg.author.mention} **>** \`${this.bot.util.escapeMarkdown(equation.replace(/ /gi, ''), false, true)}\` = \`${this.bot.util.escapeMarkdown(ex.message ? ex.message : ex, false, true)}\``, 2000)}`;
} finally { origMsg.edit(message); }
}
localize(msg) {
if (!msg) return '';
return msg.replace(/\$\[emoji#0]/g, this.bot.emote('util', 'math', '0'));
}
};

View File

@@ -0,0 +1,60 @@
const { DiscordCommand } = require('../../../core');
module.exports = class Ping extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'ping',
syntax : 'ping',
aliases : [ 'pong' ],
argument : [],
description: 'Lists latencies',
hidden : false,
enabled : true,
cooldown : 1000,
category : 'Utility',
ownerOnly : false,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
}
async execute(msg) {
const now = Date.now();
const latency = msg.channel.guild ? msg.channel.guild.shard.latency : undefined;
let health;
if (isNaN(latency)) health = msg.author.locale['util']['ping']['health'][0];
else if (latency < 200) health = `${msg.author.locale['util']['ping']['health'][1]} (**${this.bot.util.getPercentage(200, latency).toFixed()}%** \`[${'='.repeat(10 - Math.round(10* latency / 200))}${'-'.repeat(Math.round(10* latency / 200))}]\`)`;
else if (latency < 400) health = `${msg.author.locale['util']['ping']['health'][2]} (**${this.bot.util.getPercentage(400, latency).toFixed()}%** \`[${'='.repeat(10 - Math.round(10* latency / 400))}${'-'.repeat(Math.round(10* latency / 400))}]\`)`;
else if (latency > 400) health = `${msg.author.locale['util']['ping']['health'][3]} Bad (**0%** \`[----------]\`)`;
const message = await msg.channel.createMessage({
embed: {
color : this.bot.col['util']['ping']['0'],
description: this.localize(msg.author.locale['util']['ping']['busy'])
}
});
message.edit({
embed: {
color : latency < 200 ? this.bot.col['util']['ping']['1'] : latency > 200 && latency < 400 ? this.bot.col['util']['ping']['2'] : this.bot.col['util']['ping']['3'],
description: this.localize(msg.author.locale['util']['ping']['result'].join('\n'), { latency: latency, now: now, health: health })
}
});
}
localize(msg, extData = {}) {
if (!msg) return '';
if (extData && extData.now) msg = msg.replace(/\$\[roundtrip]/g, Date.now() - extData.now);
if (extData && extData.health) msg = msg.replace(/\$\[health]/g, extData.health);
if (extData && extData.latency) msg = msg.replace(/\$\[latency]/g, extData.latency);
return msg
.replace(/\$\[emoji#0]/g, this.bot.emote('util', 'ping', '0'))
.replace(/\$\[emoji#1]/g, this.bot.emote('util', 'ping', '1'))
.replace(/\$\[emoji#2]/g, this.bot.emote('util', 'ping', '2'))
.replace(/\$\[emoji#3]/g, this.bot.emote('util', 'ping', '3'));
}
};

View File

@@ -0,0 +1,72 @@
const { DiscordCommand } = require('../../../core/');
module.exports = class Snipe extends DiscordCommand {
constructor(bot) {
super(bot, {
name : 'snipe',
syntax : 'snipe',
aliases : [],
argument : [],
description: 'Re-post a deleted message',
hidden : false,
enabled : true,
cooldown : 1000,
category : 'Utility',
ownerOnly : false,
guildOnly : false,
permissions: [ 'embedLinks' ]
});
}
execute(msg) {
const message = this.bot.cache.get(`${msg.channel.id}:SNIPE`);
if (!message) return msg.channel.createMessage(this.localize(msg.author.locale['util']['snipe']['fail']));
let structure = {
author : {
name : `${message.author.username}#${message.author.discriminator} (${message.author.id})`,
icon_url: message.author.avatar ? message.author.avatarURL : message.author.defaultAvatarURL
},
color : this.bot.col['util']['snipe'],
footer : {
text : this.localize(msg.author.locale['util']['snipe']['footer'], { msg: msg }),
icon_url: msg.author.avatar ? msg.author.avatarURL : msg.author.defaultAvatarURL
},
timestamp: new Date(message.timestamp).toISOString()
};
if (message.embeds.length > 0 && 'url' in message.embeds[0]) structure.url = message.embeds[0].url;
if (message.embeds.length > 0 && 'type' in message.embeds[0]) structure.type = message.embeds[0].type;
if (message.embeds.length > 0 && 'title' in message.embeds[0]) structure.title = message.embeds[0].title;
if (message.embeds.length > 0 && 'image' in message.embeds[0]) structure.image = message.embeds[0].image;
if (message.embeds.length > 0 && 'video' in message.embeds[0]) structure.video = message.embeds[0].video;
if (message.embeds.length > 0 && 'fields' in message.embeds[0]) structure.fields = message.embeds[0].fields;
if (message.embeds.length > 0 && 'provider' in message.embeds[0]) structure.provider = message.embeds[0].provider;
if (message.embeds.length > 0 && 'thumbnail' in message.embeds[0]) structure.thumbnail = message.embeds[0].thumbnail;
if (message.embeds.length > 0 && 'description' in message.embeds[0]) structure.description = (`${message.content ? message.content : ''}\n\n${message.embeds[0].description}`).slice(0, 2048);
else if (message.content !== '' ) structure.description = message.content;
if (!('image' in structure) && message.attachments.length > 0) {
structure.image = {
url : message.attachments[0].url,
width : message.attachments[0].width,
height : message.attachments[0].height,
proxy_url: message.attachments[0].proxy_url
};
}
msg.channel.createMessage({ embed: structure });
this.bot.cache.delete(`${msg.channel.id}:SNIPE`);
}
localize(msg, extData) {
if (!msg) return '';
if (extData && extData.msg) msg = msg
.replace(/\$\[author:tag]/g, `${extData.msg.author.username}#${extData.msg.author.discriminator}`)
.replace(/\$\[author:id]/g, extData.msg.author.id);
return msg
.replace(/\$\[emoji#0]/g, this.bot.emote('util', 'snipe'));
}
};

View File

@@ -0,0 +1,97 @@
// Main
const Webhook = require('./DiscordWebhook');
const { Client: Eris } = require('eris');
// Registries
const EventRegistry = require('../registry/Discord/Events');
const LocaleRegistry = require('../registry/Discord/Locales');
const CommandRegistry = require('../registry/Discord/Commands');
// Utilities
const Util = require('../../util/Util');
const Schema = require('../internal/Discord/Schemas');
const { cyan } = require('../../util/colors');
const Collection = require('../../util/Collection');
const PermissionUtil = require('../internal/Discord/Utils/PermissionUtil');
module.exports = class WumpDiscord extends Eris {
constructor(token, options = {}) {
if (!token) throw new Error('Invalid token');
if (!options.db) throw new Error('Invalid database');
super(token, options.clientOptions);
this.hook = options.webhook ? new Webhook({ token: options.webhook.token, channel: options.webhook.channel }) : new Webhook();
this.cmds = new Collection();
this.cache = new Collection();
this.events = new Collection();
this.locales = new Collection();
this.localeMap = [];
this.localeDic = [];
this.m = options.db;
this.eventRegistry = new EventRegistry(this);
this.localeRegistry = new LocaleRegistry(this);
this.commandRegistry = new CommandRegistry(this);
this.util = Util;
this.print = Util.print;
this.schema = Schema;
this.Collection = Collection;
this.ua = options.ua ? options.ua : undefined;
this.pkg = options.pkg ? options.pkg : undefined;
this.col = options.colors ? options.colors : undefined;
this.ico = options.emojis ? options.emojis : undefined;
this.conf = options.config ? options.config : undefined;
}
async launch(events, commands, locales) {
this.eventRegistry.run(events);
this.localeRegistry.run(locales);
this.commandRegistry.run(commands);
this.connect()
.then(() => this.print(1, `${cyan('Discord')} >> Connecting shards to websockets...`));
}
op(id) { return this.conf['discord']['op'].includes(id); }
emote(a, b, c) {
if (!a || !this.ico[a]) return '';
if (c) {
if (!this.ico[a][b]) return '';
if (!this.ico[a][b][c]) return '';
if (this.ico[a][b][c].length <= 0) return '';
else return this.ico[a][b][c];
} else if (b) {
if (!this.ico[a][b]) return '';
if (this.ico[a][b].length <= 0) return '';
else return this.ico[a][b];
} else {
if (this.ico[a].length < 1) return '';
else return this.ico[a];
}
}
gatherInvite(permission) {
permission = PermissionUtil.resolve(permission);
return `https://discordapp.com/oauth2/authorize?client_id=${this.user.id}&scope=bot&permissions=${permission}`;
}
destroy(code = 0) {
let trace = { success: false, code: code };
try {
this.disconnect({ reconnect: false });
trace.success = true;
} catch ( err ) {
trace = {
err: {
code : err.code,
stack : err.stack,
message : err.message
}
};
}
return trace;
}
};

View File

@@ -0,0 +1,24 @@
const fetch = require('wumpfetch');
const { randomBytes } = require('crypto');
module.exports = class DiscordWebhook {
constructor(extData = {}) {
this.hash = randomBytes(Math.floor(Math.random() * 10) + 5).toString('hex');
this.baseURL = 'https://discordapp.com/api/webhooks';
this.extData = extData;
(async() => {
try {
if (!this.extData.token || !this.extData.channel) return this.hook = undefined;
this.hook = await fetch(`${this.baseURL}/${this.extData.channel}/${this.extData.token}`, { method: 'GET' }).send();
if (this.hook.statusCode !== 200) throw new SyntaxError( 'Failed to fetch webhook' );
} catch (ex) { throw ex; }
})();
}
async send(data = {}) {
if (!this.hook) return true;
return await fetch(`${this.baseURL}/${this.extData.channel}/${this.extData.token}`, { method: 'POST', data: data }).send();
}
};

6
src/core/index.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
DiscordEvent : require('./internal/Discord/Event'),
DiscordClient : require('./clients/Discord'),
DiscordCommand: require('./internal/Discord/Command'),
DiscordContext: require('./internal/Discord/Context')
};

View File

@@ -0,0 +1,24 @@
module.exports = class WumpCommand {
constructor(bot, opts = {}) {
this.bot = bot;
this.extData = Object.assign({
name : null, // Command name ( required )
syntax : null, // Command syntax ( optional )
aliases : [], // Command aliases ( optional )
argument : [], // Command arguments ( optional )
description : null, // Command description ( optional )
hidden : false, // Hidden from view ( true / false )
enabled : true, // Enabled or disabled ( true / false )
cooldown : 1000, // Command cooldown ( optional )
category : 'General', // Command category ( required )
ownerOnly : false, // Owner only ( true / false )
guildOnly : false, // Guild only ( true / false )
permissions : [] // Bot permissions ( optional )
}, opts);
}
async execute(msg, args, user, guild) {}
localize(msg) {}
};

View File

@@ -0,0 +1,18 @@
module.exports = class WumpContext {
constructor(bot, msg) {
Object.assign(this, msg);
this.bot = bot;
this.orig = msg;
this.locale = null;
this.prefix = null;
}
get guild() { return this.channel.guild; }
getPrefix() { return this.prefix; }
getLocale() { return this.locale; }
getMember() { return this.member; }
setPrefix(prefix) { this.prefix = prefix; }
setLocale(locale) { this.locale = locale; }
};

View File

@@ -0,0 +1,10 @@
module.exports = class WumpEvent {
constructor(bot, opts = {}) {
this.bot = bot;
this.extData = Object.assign({ name: null }, opts);
}
emit(...args) {}
localize(msg) {}
};

View File

@@ -0,0 +1,37 @@
module.exports = class RESTclient {
constructor(bot) {
this.bot = bot;
}
resolveGuild(q) {
if (q) return undefined;
const g = this.bot.guilds.get(q);
if (/^\d+$/.test(q)) return g;
else {
const gs = this.bot.guilds.filter((v) => v.name.toLowerCase().includes(q.toLowerCase()));
if (gs.length > 1) return gs[0];
else return undefined;
}
}
resolveUser(q) {
if (!q) return undefined;
if (/^\d+$/.test(q)) {
const u = this.bot.users.get(q);
if (u) return u;
} else if (/^<@!?(\d+)>$/.test(q)) {
const u = this.bot.users.get(q.match(/^<@!?(\d+)>$/)[1]);
if (u) return u;
} else if (/^(.+)#(\d{4})$/.test(q)) {
const m = q.match(/^(.+)#(\d{4})$/);
const u = this.bot.users.filter((v) => v.username === m[1] && Number(v.discriminator) === Number(m[2]));
if (u.length >= 1) return u[0];
} else {
const u = this.bot.users.filter((v) => v.username.toLowerCase().includes(q.toLowerCase()));
if (u.length >= 1) return u[0];
else return undefined;
}
}
};

View File

@@ -0,0 +1,20 @@
const mongoose = require('mongoose');
const { safeLoad } = require('js-yaml');
const { readFileSync } = require('fs');
const conf = safeLoad(readFileSync('application.yml', { encoding: 'utf8' }));
const guild = new mongoose.Schema({
guildId: { type: String, default: undefined },
prefix : { type: String, default: conf['discord']['prefix'] }
});
const user = new mongoose.Schema({
userId: { type: String, default: undefined },
locale: { type: String, default: conf['discord']['locale'] }
});
module.exports = {
user : mongoose.model('dUsers', user, 'dUsers'),
guild: mongoose.model('dGuilds', guild, 'dGuilds')
};

View File

@@ -0,0 +1,10 @@
const { Constants } = require('eris');
module.exports = class PermissionUtil {
static resolve(permission) {
if (typeof permission === 'number' && permission >= 0) return permission;
if (typeof permission === 'string') return Constants.permission[permission];
if (permission instanceof Array) return permission.map((v) => this.resolve(v)).reduce((prev, p) => prev | p, 0);
throw new RangeError('Invalid permission');
}
};

View File

@@ -0,0 +1,25 @@
const { DiscordCommand } = require('../../../core');
module.exports = class name extends DiscordCommand {
constructor(bot) {
super(bot, {
name : '',
syntax : '',
aliases : [],
argument : [],
description : '',
hidden : false,
enabled : true,
cooldown : 1000,
category : 'Utility',
ownerOnly : false,
guildOnly : false,
permissions : [ 'embedLinks' ]
});
}
async execute(msg, args, user, guild) {}
localize(msg) {}
};

View File

@@ -0,0 +1,183 @@
const moment = require('moment'); require('../../../util/moment/format');
const { safeLoad } = require('js-yaml');
const { Collection } = require('eris');
const { red, green, yellow, cyan } = require('../../../util/colors');
const { readdir, readdirSync, readFileSync } = require('fs');
const Context = require('../../internal/Discord/Context');
const startTimestamp = Date.now();
module.exports = class CommandRegistry {
constructor(bot) {
this.bot = bot;
this.ratelimits = new Collection();
}
start(directory, category, file) {
try {
const cmd = new(require(`${directory}/${category}/${file}`))(this.bot);
cmd.extData.location = `${directory}/${category}/${file}`;
if (!cmd.extData.enabled) return;
if (!this.bot.conf['discord']['commands'][cmd.extData.name]) return;
if (this.bot.cmds.has(cmd.extData.name)) return this.bot.print(1, `${cyan('Discord')} -- Duplicate command found - ${red(`${directory}/${category}/${file}`)}` );
this.bot.cmds.set(cmd.extData.name, cmd);
this.bot.print(2, `${cyan('Discord')} ++ Loaded command ${green(cmd.extData.name)} in ${yellow(`${(Date.now() - startTimestamp)}ms`)}`);
} catch (ex) { this.bot.print(1, `${cyan('Discord')} >> ${yellow(`${directory}/${category}/${file}`)} ${red(`is not a valid command: ${ex.message}`)}`); }
}
async run(directory) {
const categories = await readdirSync(directory);
for (let i = 0; i < categories.length; i++) {
if (categories[i].endsWith('md')) return;
readdir(`${directory}/${categories[i]}`, (err, cmds) => {
if (err) process.handleError(err, cyan('Discord'), 'LoadError');
this.bot.print(1, `${cyan('Discord')} >> Loading ${green(cmds.length)} commands from category ${yellow(categories[i])}` );
cmds.forEach((cmd) => {
try {
this.start(directory, categories[i], cmd);
} catch(ex) { process.handleError(err, cyan('Discord'), 'LoadError'); }
});
});
}
}
reload(command = 'ping') {
const cmd = this.bot.cmds.get(command);
const dir = cmd ? cmd.extData.location : undefined;
if (!cmd || !dir) return false;
delete require.gCache[require.resolve(dir)];
this.bot.cmds.delete(command);
this.start(dir);
return true;
}
async handleCommand(msg) {
let perm;
let prefix;
let guild, user;
let gCache, uCache;
if (msg.author.bot || !this.bot.ready) return;
if (msg.channel.guild) {
perm = msg.channel.permissionsOf(this.bot.user.id);
gCache = this.bot.cache.has('guilds') ? this.bot.cache.get('guilds') : this.bot.cache.set('guilds', []) && this.bot.cache.get('guilds');
uCache = this.bot.cache.has('users') ? this.bot.cache.get('users') : this.bot.cache.set('users', []) && this.bot.cache.get('users');
if (!this.checkArray('guildId', msg.channel.guild.id, gCache)) {
guild = await this.bot.m.connection.collection('dGuilds').findOne({ guildId: msg.channel.guild.id });
if (!guild) {
guild = new this.bot.schema.guild({ guildId: msg.channel.guild.id });
await guild.save((err) => { if (err) process.handleError(err); });
} else gCache.push({ 'guildId': msg.channel.guild.id, 'prefix': guild.prefix, 'entryAge': Date.now() });
} else guild = gCache.filter((v) => v['guildId'] === msg.channel.guild.id );
if (!this.checkArray('userId', msg.author.id, uCache)) {
user = await this.bot.m.connection.collection('dUsers').findOne({ userId: msg.author.id });
if (!user) {
user = new this.bot.schema.user({ userId: msg.author.id });
await user.save((err) => { if (err) process.handleError(err); });
} else uCache.push({ 'userId': msg.author.id, locale: user.locale ? user.locale : this.bot.conf['discord']['locale'], 'entryAge': Date.now() });
} else user = gCache.filter((v) => v['userId'] === msg.author.id );
msg.author.locale = this.bot.locales.get(user['locale'] ? user['locale'] : 'en_us');
prefix = new RegExp([
`^<@!?${this.bot.user.id}> `,
`^${this.bot.conf['discord']['prefix'].replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}`,
`^${guild && guild['prefix'] ? guild['prefix'].replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') : this.bot.conf['discord']['prefix'].replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}`
].join('|')).exec(msg.content.toLowerCase());
} else {
guild = undefined;
prefix = /(?:)/.exec(msg['content'].toLowerCase());
}
if (!prefix) return;
const ctx = new Context(this.bot, msg);
ctx.setPrefix(prefix);
const args = msg.content.slice(prefix[0].length).trim().split(/ +/g);
let cmd = args.shift().toLowerCase();
cmd = this.bot.cmds.filter((v) => v.extData.name.toLowerCase() === cmd || v.extData.aliases.includes(cmd));
if (cmd.length > 0) {
user['entryAge'] = Date.now();
guild['entryAge'] = Date.now();
cmd = cmd[0];
if (cmd.extData.ownerOnly && !this.bot.op(msg.author.id))
return msg.channel.createMessage(`${this.bot.emote('owner_only')} ${msg.author.mention} **>** You don't have enough permissions to execute this command (\`BOT_OP\`)`);
if (cmd.extData.guildOnly && msg.channel.type === 1)
return msg.channel.createMessage( `${this.bot.emote('guild_only')} ${msg.author.mention} **>** This command can only be used in guilds` );
if (msg.channel.guild && (cmd.extData.permissions && cmd.extData.permissions.some((v) => !perm.has(v))) ||
msg.channel.guild && !perm.has('sendMessages')) return;
if (!this.ratelimits.has(cmd.extData.name)) this.ratelimits.set(cmd.extData.name, []);
const now = Date.now();
const timestamps = this.ratelimits.get(cmd.extData.name);
const cooldownUser = timestamps.find((v) => v.authorId === msg.author.id);
const cooldownAmount = !this.bot.op(msg.author.id) ? (cmd.extData.cooldown) : 0;
if (!cooldownUser) {
timestamps.push({ authorId: msg.author.id, date: now });
setTimeout(() => {
const _ = timestamps.findIndex((v) => v.authorId === msg.author.id);
if (_ > -1) timestamps.splice(_, 1);
else return false;
}, cooldownAmount);
} else {
const expirationTime = cooldownUser.date + cooldownAmount;
if (now <= expirationTime) {
const timeLeft = (expirationTime - now);
return msg.channel.createMessage({
embed: {
color : this.bot.col['cooldown'],
description: this.localize(msg.author.locale['cooldown'], { left: moment.duration(timeLeft).format('YYYY[y] M[M] DD[d] H[h] m[m] s[S] SS[ms]'), author: msg.author })
}
});
}
}
try {
await cmd.execute(ctx, args, user, guild);
this.bot.print(3, `${cyan('Discord')} >> event "${yellow('COMMAND_EXECUTE')}" fired`);
this.bot.print(3, `${cyan(`DShard #${msg.channel.guild ? msg.channel.guild.shard.id : 0}`)} >> Command '${cmd.extData.name}' executed by ${yellow(msg.author.id)}`);
} catch (ex) {
process.handleError(ex, cyan('Discord'), 'CommandError');
msg.channel.createMessage({
embed: {
color : this.bot.col['error'],
description: this.localize(msg.author.locale['error'].join('\n'), { author: msg.author, err: ex })
}
});
}
}
}
checkArray(field, value, array) {
if (array instanceof Array) {
return array.some((v) => { return v[field] === value; });
} else return false;
}
localize(msg, extData) {
if (!msg) return '';
if (extData.err) msg = msg
.replace(/\$\[err:code]/g, extData.err.code)
.replace(/\$\[err:message]/g, extData.err.message.replace(/"/gi, '\\"'));
if (extData.left) msg = msg.replace(/\$\[cooldown:left]/g, extData.left);
if (extData.author) msg = msg.replace(/\$\[author:mention]/g, extData.author.mention);
return msg.replace(/\$\[emoji#0]/g, this.bot.emote('cooldown'));
}
};

View File

@@ -0,0 +1,40 @@
const { readdirSync } = require('fs');
const { red, green, yellow, cyan } = require('../../../util/colors');
module.exports = class EventStore {
constructor(bot) {
this.bot = bot;
}
execute(event) {
const execAsync = async(...args) => {
try { await event.emit(...args); }
catch (ex) { process.handleError(ex, cyan('Discord'), 'EventError'); }
};
this.bot.on(event.extData.name, execAsync);
}
async run(directory) {
const startTimestamp = Date.now();
const events = await readdirSync(directory);
this.bot.print(1, `${cyan('Discord')} >> Loading ${green(events.length)} events...`);
events.forEach((e) => {
if (!e.endsWith('.js' )) return;
const event = new(require(`${directory}/${e}`))(this.bot);
if (!this.bot.conf['discord']['events'][event.extData.name.toLowerCase()]) return false;
if (this.bot.events.has(event.extData.name)) return this.bot.print(1, `[${cyan('Discord')}] -- Duplicate event found - ${red(`${directory}/${event.extData.name}`)}`);
this.bot.events.set(event.extData.name, event);
this.bot.print(2, `${cyan('Discord')} ++ Loaded event ${green(event.extData.name)} in ${yellow(`${(Date.now() - startTimestamp)}ms`)}`);
this.execute(event);
});
this.bot.print(2, `${cyan('Discord')} >> Successfully loaded ${green(this.bot.events.size)} events`);
return { success: true, directory: directory, events: this.bot.events };
}
};

View File

@@ -0,0 +1,34 @@
const { safeLoad } = require('js-yaml');
const { cyan, red } = require('../../../util/colors');
const { readdirSync, readFileSync } = require('fs');
module.exports = class LocaleRegistry {
constructor(bot) {
this.bot = bot;
}
async run(directory) {
const locales = await readdirSync(directory);
for (let i = 0; i < locales.length; i++) {
const files = await readdirSync(`${directory}/${locales[i]}`);
files.forEach((f) => {
try {
if (f.endsWith('.yml') || f.endsWith('.yaml') ) {
const l = safeLoad(readFileSync(`${directory}/${locales[i]}/${f}`));
if (f.startsWith('strings')) {
this.bot.locales.set(l['translation']['code'].toLowerCase(), l);
this.bot.localeDic.push(l['translation']['code']);
}
}
} catch (ex) {
process.handleError(ex, cyan('Discord'), 'LoadError');
return { success: false, err: ex };
}
return { success: true, dictionary: this.bot.localeDic, map: this.bot.localeMap };
});
}
}
};

View File

@@ -0,0 +1,13 @@
const { DiscordEvent } = require('../../core');
const { green, cyan } = require('../../util/colors');
module.exports = class Disconnect extends DiscordEvent {
constructor(bot) {
super(bot, { name: 'disconnect' });
}
emit() {
this.bot.print(1, `${cyan('Discord')} >> All websockets disconnected from ${green(`${this.bot.user.username}#${this.bot.user.discriminator}`)}`);
}
};

View File

@@ -0,0 +1,11 @@
const { DiscordEvent } = require('../../core');
module.exports = class MessageCreate extends DiscordEvent {
constructor(bot) {
super(bot, { name: 'messageCreate' });
}
async emit(msg) {
this.bot.commandRegistry.handleCommand(msg);
}
};

View File

@@ -0,0 +1,12 @@
const { DiscordEvent } = require('../../core');
module.exports = class MessageDelete extends DiscordEvent {
constructor(bot) {
super(bot, { name: 'messageDelete' });
}
emit(msg) {
if (!msg.channel.guild || !msg.author || !this.bot.ready) return;
this.bot.cache.set(`${msg.channel.id}:SNIPE`, msg);
}
};

View File

@@ -0,0 +1,32 @@
const { DiscordEvent } = require('../../core');
const { green, cyan } = require('../../util/colors');
module.exports = class Ready extends DiscordEvent {
constructor(bot) {
super(bot, { name: 'ready' });
}
emit() {
this.bot.print(1, `${cyan( 'Discord' )} >> All websockets connected to ${green(`${this.bot.user.username}#${this.bot.user.discriminator}`)}`);
if (this.bot.conf['discord']['playing']) {
this.bot.editStatus({
url : this.bot.conf['discord']['playing']['url'] || null,
type : this.bot.conf['discord']['playing']['type'] || 3,
name : this.bot.conf['discord']['playing']['name'] || 'Wump',
status : this.bot.conf['discord']['playing']['status'] || 'online'
});
}
// Cache flushing if entry isn't used within `flushTime` minutes
const flushTime = 1800000;
setInterval(() => {
const uCache = this.bot.cache.get('users') || [];
const gCache = this.bot.cache.get('guilds') || [];
uCache.forEach((v, _) => (new Date(v.entryAge) <= new Date(new Date() - flushTime)) ? uCache.splice(_, 1) : undefined);
gCache.forEach((v, _) => (new Date(v.entryAge) <= new Date(new Date() - flushTime)) ? gCache.splice(_, 1) : undefined);
}, flushTime);
}
};

94
src/main.js Normal file
View File

@@ -0,0 +1,94 @@
/**
* @name Wunp
* @author Wessel "wesselgame" T < discord@go2it.eu >
* @license GPL-3.0
* @version 0.1.1
* @description A multi-functional bot written in Node.js
*/
console.clear();
const mongoose = require('mongoose');
const { DiscordClient } = require('./core');
const { print } = require('./util/Util');
const { safeLoad } = require('js-yaml');
const { globalize } = require('nabbit');
const { join: pJoin } = require('path');
const { readFileSync } = require('fs');
const { magenta, green, cyan, yellow, red } = require('./util/colors');
globalize();
let Discord, Database;
process.argv = require('larg')(process.argv.slice(2));
process.hash = String.string(Math.floor(Math.random() * 5) + 3);
process.handleError = (err, name, type, exit = false, custom = false) => {
if (!custom) print(0, `${type ? type : green('Master')} !! ${name ? name : err.name ? err.name : 'Error'} -\n${red(`${err.message && err.stack ? `${err.message}\n${err.stack}` : err}`)}`);
else print(0, custom);
if (exit) process.exit(process.type(exit) === 8 ? exit : 1);
};
print(3, `${magenta('Debug')} >> Loading configuration files...`);
const pkg = require('../package.json');
const ico = safeLoad(readFileSync( 'assets/config/emotes.yml'));
const col = safeLoad(readFileSync( 'assets/config/colors.yml'));
const conf = safeLoad(readFileSync('application.yml'));
print(3, `${magenta('Debug')} >> Configuration files loaded`);
process.on('uncaughtException', process.handleError);
process.on('unhandledRejection', process.handleError);
process.on('SIGINT', () => {
if (Database) {
try {
print(2, `${yellow('Database')} >> Closing connection from database...`);
mongoose.connection.close();
} catch (ex) {
print(2, `${yellow('Database')} >> Failed to close connection from database - ${red(`${ex.message}\n${ex.stack}`)}`);
process.exit(1);
} finally { print(2, `${yellow('Database')} >> ${green('Connection from database closed')}`); }
}
if (Discord) {
try {
print(2, `${cyan('Discord')} >> Disconnecting websockets...`);
Discord.destroy(0);
} catch (ex) {
print(2, `${cyan('Discord' )} >> Failed to disconnect websockets - ${red(`${ex.message}\n${ex.stack}`)}`);
process.exit(1);
} finally { print(2, `${cyan('Discord')} >> ${green('Connection from websockets closed')}`); }
}
process.exit(0);
});
(async() => {
if (!conf['db']) process.handleError(undefined, undefined, undefined, 1, red('!! Failed to start: No database found !!'));
print(2, `${yellow('Database' )} >> Connecting to database...`);
Database = mongoose.connect(conf['db'], { useNewUrlParser: true });
mongoose.connection.on('error', process.handleError);
print(2, `${yellow('Database')} >> ${green('Connected to database')}` );
})();
if (conf['discord']['enabled']) {
print(2, `${cyan('Discord')} >> Creating client...`);
Discord = new DiscordClient(conf['discord']['token'], {
clientOptions : {
maxShards : 'auto',
getAllUsers : false,
autoreconnect: true
},
ua : `${pkg.displayName}/${pkg.version}/${conf['nightly'] ? 'nightly' : 'distribution'}`,
db : mongoose,
pkg : pkg,
colors: col,
emojis: ico,
config: conf
});
print(2, `${cyan('Discord')} >> Client created, launching client...`);
Discord.launch(pJoin(__dirname, 'events', 'Discord'), pJoin(__dirname, 'commands', 'Discord'), pJoin(__dirname, 'assets', 'i18n'), pJoin(__dirname, 'schedulers', 'Discord'));
}

23
src/util/Collection.js Normal file
View File

@@ -0,0 +1,23 @@
module.exports = class WumpCollection extends Map {
constructor() { super(); }
filter(callback) {
const all = Array.from(this.values());
let result = [];
for (let i = 0; i < all.length; i++) {
if (callback(all[i])) result.push(all[i]);
}
return result;
}
map(callback) {
let result = [];
const values = Array.from(this.values());
for (let i = 0; i < values.length; i++) {
result.push(callback(values[i]));
}
return result;
}
};

47
src/util/Util.js Normal file
View File

@@ -0,0 +1,47 @@
const fs = require('fs');
const yaml = require('js-yaml');
const moment = require('moment');
const { strip } = require('../util/colors');
const conf = yaml.safeLoad(fs.readFileSync('application.yml', { encoding: 'utf8' }));
module.exports = class WumpUtil {
static print(lvl = 0, msg = '') {
const log = `tmp\\log\\${moment(new Date).format('DD[-]MM[-]YYYY')}.log`;
fs.mkdir('tmp', 777, (err) => { if (err && err.code !== 'EEXIST') throw err; });
fs.mkdir('tmp\\log', 777, (err) => { if (err && err.code !== 'EEXIST') throw err; });
switch (lvl) {
case 1 && conf['debug'] && conf['debug'] >= 1:
console.log(`[${moment(new Date).format( 'HH[:]mm[:]ss' )} / ${process.hash}] ${msg}`);
fs.appendFileSync(log, `[${moment(new Date).format('HH[:]mm[:]ss')} / ${process.hash}] ${strip(msg)}\r\n`);
break;
case 2 && conf['debug'] && conf['debug'] >= 2:
console.log(`[${moment(new Date).format( 'HH[:]mm[:]ss' )} / ${process.hash}] ${msg}`);
fs.appendFileSync(log, `[${moment(new Date).format('HH[:]mm[:]ss')} / ${process.hash}] ${strip(msg)}\r\n`);
break;
case 3 && conf['debug'] && conf['debug'] >= 3:
console.log(`[${moment(new Date).format( 'HH[:]mm[:]ss' )} / ${process.hash}] ${msg}`);
fs.appendFileSync(log, `[${moment(new Date).format('HH[:]mm[:]ss')} / ${process.hash}] ${strip(msg)}\r\n`);
break;
case 4 && conf['debug'] && conf['debug'] >= 4:
console.log(`[${moment(new Date).format( 'HH[:]mm[:]ss' )} / ${process.hash}] ${msg}`);
fs.appendFileSync(log, `[${moment(new Date).format('HH[:]mm[:]ss')} / ${process.hash}] ${strip(msg)}\r\n`);
break;
default:
console.log(`[${moment(new Date).format( 'HH[:]mm[:]ss' )} / ${process.hash}] ${msg}`);
fs.appendFileSync(log, `[${moment(new Date).format('HH[:]mm[:]ss')} / ${process.hash}] ${strip(msg)}\r\n`);
}
}
static getPercentage(o, n) { return ( ( o - n ) / o ) * 100; }
static shorten(t = '', m = 2000) { return t.length > m ? `${t.substr( 0, m - 3 )}...` : t; }
static escapeMarkdown(t = '', oc = false, oi = false) {
if (oc) return t.replace(/```/g, '\u200b`\u200b`\u200b`');
if (oi) return t.replace(/\\([`\\])/g, '$1').replace(/([`\\])/g, '\u200b$1');
return t.replace(/([*_`~\\])/g , '\u200b$1').replace(/\\([*_`~\\])/g, '$1');
}
};

4
src/util/colors/index.js Normal file
View File

@@ -0,0 +1,4 @@
var colors = require('./lib/colors');
module['exports'] = colors;
require('./lib/extendStringPrototype')();

View File

@@ -0,0 +1,201 @@
/*
The MIT License (MIT)
Original Library
- Copyright (c) Marak Squires
Additional functionality
- Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
var colors = {};
module['exports'] = colors;
colors.themes = {};
var util = require('util');
var ansiStyles = colors.styles = require('./styles');
var defineProps = Object.defineProperties;
var newLineRegex = new RegExp(/[\r\n]+/g);
colors.supportsColor = require('./system/supports-colors').supportsColor;
if (typeof colors.enabled === 'undefined') {
colors.enabled = colors.supportsColor() !== false;
}
colors.enable = function() {
colors.enabled = true;
};
colors.disable = function() {
colors.enabled = false;
};
colors.stripColors = colors.strip = function(str) {
return ('' + str).replace(/\x1B\[\d+m/g, '');
};
// eslint-disable-next-line no-unused-vars
var stylize = colors.stylize = function stylize(str, style) {
if (!colors.enabled) {
return str+'';
}
return ansiStyles[style].open + str + ansiStyles[style].close;
};
var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
var escapeStringRegexp = function(str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str.replace(matchOperatorsRe, '\\$&');
};
function build(_styles) {
var builder = function builder() {
return applyStyle.apply(builder, arguments);
};
builder._styles = _styles;
// __proto__ is used because we must return a function, but there is
// no way to create a function with a different prototype.
builder.__proto__ = proto;
return builder;
}
var styles = (function() {
var ret = {};
ansiStyles.grey = ansiStyles.gray;
Object.keys(ansiStyles).forEach(function(key) {
ansiStyles[key].closeRe =
new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g');
ret[key] = {
get: function() {
return build(this._styles.concat(key));
}
};
});
return ret;
})();
var proto = defineProps(function colors() {}, styles);
function applyStyle() {
var args = Array.prototype.slice.call(arguments);
var str = args.map(function(arg) {
if (arg !== undefined && arg.constructor === String) {
return arg;
} else {
return util.inspect(arg);
}
}).join(' ');
if (!colors.enabled || !str) {
return str;
}
var newLinesPresent = str.indexOf('\n') != -1;
var nestedStyles = this._styles;
var i = nestedStyles.length;
while (i--) {
var code = ansiStyles[nestedStyles[i]];
str = code.open + str.replace(code.closeRe, code.open) + code.close;
if (newLinesPresent) {
str = str.replace(newLineRegex, function(match) {
return code.close + match + code.open;
});
}
}
return str;
}
colors.setTheme = function(theme) {
if (typeof theme === 'string') {
console.log('colors.setTheme now only accepts an object, not a string. ' +
'If you are trying to set a theme from a file, it is now your (the ' +
'caller\'s) responsibility to require the file. The old syntax ' +
'looked like colors.setTheme(__dirname + ' +
'\'/../themes/generic-logging.js\'); The new syntax looks like '+
'colors.setTheme(require(__dirname + ' +
'\'/../themes/generic-logging.js\'));');
return;
}
for (var style in theme) {
(function(style) {
colors[style] = function(str) {
if (typeof theme[style] === 'object') {
var out = str;
for (var i in theme[style]) {
out = colors[theme[style][i]](out);
}
return out;
}
return colors[theme[style]](str);
};
})(style);
}
};
function init() {
var ret = {};
Object.keys(styles).forEach(function(name) {
ret[name] = {
get: function() {
return build([name]);
}
};
});
return ret;
}
var sequencer = function sequencer(map, str) {
var exploded = str.split('');
exploded = exploded.map(map);
return exploded.join('');
};
// custom formatter methods
colors.trap = require('./custom/trap');
colors.zalgo = require('./custom/zalgo');
// maps
colors.maps = {};
colors.maps.america = require('./maps/america')(colors);
colors.maps.zebra = require('./maps/zebra')(colors);
colors.maps.rainbow = require('./maps/rainbow')(colors);
colors.maps.random = require('./maps/random')(colors);
for (var map in colors.maps) {
(function(map) {
colors[map] = function(str) {
return sequencer(colors.maps[map], str);
};
})(map);
}
defineProps(colors, init());

View File

@@ -0,0 +1,46 @@
module['exports'] = function runTheTrap(text, options) {
var result = '';
text = text || 'Run the trap, drop the bass';
text = text.split('');
var trap = {
a: ['\u0040', '\u0104', '\u023a', '\u0245', '\u0394', '\u039b', '\u0414'],
b: ['\u00df', '\u0181', '\u0243', '\u026e', '\u03b2', '\u0e3f'],
c: ['\u00a9', '\u023b', '\u03fe'],
d: ['\u00d0', '\u018a', '\u0500', '\u0501', '\u0502', '\u0503'],
e: ['\u00cb', '\u0115', '\u018e', '\u0258', '\u03a3', '\u03be', '\u04bc',
'\u0a6c'],
f: ['\u04fa'],
g: ['\u0262'],
h: ['\u0126', '\u0195', '\u04a2', '\u04ba', '\u04c7', '\u050a'],
i: ['\u0f0f'],
j: ['\u0134'],
k: ['\u0138', '\u04a0', '\u04c3', '\u051e'],
l: ['\u0139'],
m: ['\u028d', '\u04cd', '\u04ce', '\u0520', '\u0521', '\u0d69'],
n: ['\u00d1', '\u014b', '\u019d', '\u0376', '\u03a0', '\u048a'],
o: ['\u00d8', '\u00f5', '\u00f8', '\u01fe', '\u0298', '\u047a', '\u05dd',
'\u06dd', '\u0e4f'],
p: ['\u01f7', '\u048e'],
q: ['\u09cd'],
r: ['\u00ae', '\u01a6', '\u0210', '\u024c', '\u0280', '\u042f'],
s: ['\u00a7', '\u03de', '\u03df', '\u03e8'],
t: ['\u0141', '\u0166', '\u0373'],
u: ['\u01b1', '\u054d'],
v: ['\u05d8'],
w: ['\u0428', '\u0460', '\u047c', '\u0d70'],
x: ['\u04b2', '\u04fe', '\u04fc', '\u04fd'],
y: ['\u00a5', '\u04b0', '\u04cb'],
z: ['\u01b5', '\u0240'],
};
text.forEach(function(c) {
c = c.toLowerCase();
var chars = trap[c] || [' '];
var rand = Math.floor(Math.random() * chars.length);
if (typeof trap[c] !== 'undefined') {
result += trap[c][rand];
} else {
result += c;
}
});
return result;
};

View File

@@ -0,0 +1,110 @@
// please no
module['exports'] = function zalgo(text, options) {
text = text || ' he is here ';
var soul = {
'up': [
'̍', '̎', '̄', '̅',
'̿', '̑', '̆', '̐',
'͒', '͗', '͑', '̇',
'̈', '̊', '͂', '̓',
'̈', '͊', '͋', '͌',
'̃', '̂', '̌', '͐',
'̀', '́', '̋', '̏',
'̒', '̓', '̔', '̽',
'̉', 'ͣ', 'ͤ', 'ͥ',
'ͦ', 'ͧ', 'ͨ', 'ͩ',
'ͪ', 'ͫ', 'ͬ', 'ͭ',
'ͮ', 'ͯ', '̾', '͛',
'͆', '̚',
],
'down': [
'̖', '̗', '̘', '̙',
'̜', '̝', '̞', '̟',
'̠', '̤', '̥', '̦',
'̩', '̪', '̫', '̬',
'̭', '̮', '̯', '̰',
'̱', '̲', '̳', '̹',
'̺', '̻', '̼', 'ͅ',
'͇', '͈', '͉', '͍',
'͎', '͓', '͔', '͕',
'͖', '͙', '͚', '̣',
],
'mid': [
'̕', '̛', '̀', '́',
'͘', '̡', '̢', '̧',
'̨', '̴', '̵', '̶',
'͜', '͝', '͞',
'͟', '͠', '͢', '̸',
'̷', '͡', ' ҉',
],
};
var all = [].concat(soul.up, soul.down, soul.mid);
function randomNumber(range) {
var r = Math.floor(Math.random() * range);
return r;
}
function isChar(character) {
var bool = false;
all.filter(function(i) {
bool = (i === character);
});
return bool;
}
function heComes(text, options) {
var result = '';
var counts;
var l;
options = options || {};
options['up'] =
typeof options['up'] !== 'undefined' ? options['up'] : true;
options['mid'] =
typeof options['mid'] !== 'undefined' ? options['mid'] : true;
options['down'] =
typeof options['down'] !== 'undefined' ? options['down'] : true;
options['size'] =
typeof options['size'] !== 'undefined' ? options['size'] : 'maxi';
text = text.split('');
for (l in text) {
if (isChar(l)) {
continue;
}
result = result + text[l];
counts = {'up': 0, 'down': 0, 'mid': 0};
switch (options.size) {
case 'mini':
counts.up = randomNumber(8);
counts.mid = randomNumber(2);
counts.down = randomNumber(8);
break;
case 'maxi':
counts.up = randomNumber(16) + 3;
counts.mid = randomNumber(4) + 1;
counts.down = randomNumber(64) + 3;
break;
default:
counts.up = randomNumber(8) + 1;
counts.mid = randomNumber(6) / 2;
counts.down = randomNumber(8) + 1;
break;
}
var arr = ['up', 'mid', 'down'];
for (var d in arr) {
var index = arr[d];
for (var i = 0; i <= counts[index]; i++) {
if (options[index]) {
result = result + soul[index][randomNumber(soul[index].length)];
}
}
}
}
return result;
}
// don't summon him
return heComes(text, options);
};

View File

@@ -0,0 +1,104 @@
var colors = require('./colors');
module['exports'] = function() {
//
// Extends prototype of native string object to allow for "foo".red syntax
//
var addProperty = function(color, func) {
String.prototype.__defineGetter__(color, func);
};
addProperty('strip', function() {
return colors.strip(this);
});
addProperty('stripColors', function() {
return colors.strip(this);
});
addProperty('trap', function() {
return colors.trap(this);
});
addProperty('zalgo', function() {
return colors.zalgo(this);
});
addProperty('zebra', function() {
return colors.zebra(this);
});
addProperty('rainbow', function() {
return colors.rainbow(this);
});
addProperty('random', function() {
return colors.random(this);
});
addProperty('america', function() {
return colors.america(this);
});
//
// Iterate through all default styles and colors
//
var x = Object.keys(colors.styles);
x.forEach(function(style) {
addProperty(style, function() {
return colors.stylize(this, style);
});
});
function applyTheme(theme) {
//
// Remark: This is a list of methods that exist
// on String that you should not overwrite.
//
var stringPrototypeBlacklist = [
'__defineGetter__', '__defineSetter__', '__lookupGetter__',
'__lookupSetter__', 'charAt', 'constructor', 'hasOwnProperty',
'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString',
'valueOf', 'charCodeAt', 'indexOf', 'lastIndexOf', 'length',
'localeCompare', 'match', 'repeat', 'replace', 'search', 'slice',
'split', 'substring', 'toLocaleLowerCase', 'toLocaleUpperCase',
'toLowerCase', 'toUpperCase', 'trim', 'trimLeft', 'trimRight',
];
Object.keys(theme).forEach(function(prop) {
if (stringPrototypeBlacklist.indexOf(prop) !== -1) {
console.log('warn: '.red + ('String.prototype' + prop).magenta +
' is probably something you don\'t want to override. ' +
'Ignoring style name');
} else {
if (typeof(theme[prop]) === 'string') {
colors[prop] = colors[theme[prop]];
} else {
var tmp = colors[theme[prop][0]];
for (var t = 1; t < theme[prop].length; t++) {
tmp = tmp[theme[prop][t]];
}
colors[prop] = tmp;
}
addProperty(prop, function() {
return colors[prop](this);
});
}
});
}
colors.setTheme = function(theme) {
if (typeof theme === 'string') {
console.log('colors.setTheme now only accepts an object, not a string. ' +
'If you are trying to set a theme from a file, it is now your (the ' +
'caller\'s) responsibility to require the file. The old syntax ' +
'looked like colors.setTheme(__dirname + ' +
'\'/../themes/generic-logging.js\'); The new syntax looks like '+
'colors.setTheme(require(__dirname + ' +
'\'/../themes/generic-logging.js\'));');
return;
} else {
applyTheme(theme);
}
};
};

View File

@@ -0,0 +1,10 @@
module['exports'] = function(colors) {
return function(letter, i, exploded) {
if (letter === ' ') return letter;
switch (i%3) {
case 0: return colors.red(letter);
case 1: return colors.white(letter);
case 2: return colors.blue(letter);
}
};
};

View File

@@ -0,0 +1,12 @@
module['exports'] = function(colors) {
// RoY G BiV
var rainbowColors = ['red', 'yellow', 'green', 'blue', 'magenta'];
return function(letter, i, exploded) {
if (letter === ' ') {
return letter;
} else {
return colors[rainbowColors[i++ % rainbowColors.length]](letter);
}
};
};

View File

@@ -0,0 +1,10 @@
module['exports'] = function(colors) {
var available = ['underline', 'inverse', 'grey', 'yellow', 'red', 'green',
'blue', 'white', 'cyan', 'magenta'];
return function(letter, i, exploded) {
return letter === ' ' ? letter :
colors[
available[Math.round(Math.random() * (available.length - 2))]
](letter);
};
};

View File

@@ -0,0 +1,5 @@
module['exports'] = function(colors) {
return function(letter, i, exploded) {
return i % 2 === 0 ? letter : colors.inverse(letter);
};
};

View File

@@ -0,0 +1,77 @@
/*
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
var styles = {};
module['exports'] = styles;
var codes = {
reset: [0, 0],
bold: [1, 22],
dim: [2, 22],
italic: [3, 23],
underline: [4, 24],
inverse: [7, 27],
hidden: [8, 28],
strikethrough: [9, 29],
black: [30, 39],
red: [31, 39],
green: [32, 39],
yellow: [33, 39],
blue: [34, 39],
magenta: [35, 39],
cyan: [36, 39],
white: [37, 39],
gray: [90, 39],
grey: [90, 39],
bgBlack: [40, 49],
bgRed: [41, 49],
bgGreen: [42, 49],
bgYellow: [43, 49],
bgBlue: [44, 49],
bgMagenta: [45, 49],
bgCyan: [46, 49],
bgWhite: [47, 49],
// legacy styles for colors pre v1.0.0
blackBG: [40, 49],
redBG: [41, 49],
greenBG: [42, 49],
yellowBG: [43, 49],
blueBG: [44, 49],
magentaBG: [45, 49],
cyanBG: [46, 49],
whiteBG: [47, 49],
};
Object.keys(codes).forEach(function(key) {
var val = codes[key];
var style = styles[key] = [];
style.open = '\u001b[' + val[0] + 'm';
style.close = '\u001b[' + val[1] + 'm';
});

View File

@@ -0,0 +1,35 @@
/*
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
'use strict';
module.exports = function(flag, argv) {
argv = argv || process.argv;
var terminatorPos = argv.indexOf('--');
var prefix = /^-{1,2}/.test(flag) ? '' : '--';
var pos = argv.indexOf(prefix + flag);
return pos !== -1 && (terminatorPos === -1 ? true : pos < terminatorPos);
};

View File

@@ -0,0 +1,151 @@
/*
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
'use strict';
var os = require('os');
var hasFlag = require('./has-flag.js');
var env = process.env;
var forceColor = void 0;
if (hasFlag('no-color') || hasFlag('no-colors') || hasFlag('color=false')) {
forceColor = false;
} else if (hasFlag('color') || hasFlag('colors') || hasFlag('color=true')
|| hasFlag('color=always')) {
forceColor = true;
}
if ('FORCE_COLOR' in env) {
forceColor = env.FORCE_COLOR.length === 0
|| parseInt(env.FORCE_COLOR, 10) !== 0;
}
function translateLevel(level) {
if (level === 0) {
return false;
}
return {
level: level,
hasBasic: true,
has256: level >= 2,
has16m: level >= 3,
};
}
function supportsColor(stream) {
if (forceColor === false) {
return 0;
}
if (hasFlag('color=16m') || hasFlag('color=full')
|| hasFlag('color=truecolor')) {
return 3;
}
if (hasFlag('color=256')) {
return 2;
}
if (stream && !stream.isTTY && forceColor !== true) {
return 0;
}
var min = forceColor ? 1 : 0;
if (process.platform === 'win32') {
// Node.js 7.5.0 is the first version of Node.js to include a patch to
// libuv that enables 256 color output on Windows. Anything earlier and it
// won't work. However, here we target Node.js 8 at minimum as it is an LTS
// release, and Node.js 7 is not. Windows 10 build 10586 is the first
// Windows release that supports 256 colors. Windows 10 build 14931 is the
// first release that supports 16m/TrueColor.
var osRelease = os.release().split('.');
if (Number(process.versions.node.split('.')[0]) >= 8
&& Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
return Number(osRelease[2]) >= 14931 ? 3 : 2;
}
return 1;
}
if ('CI' in env) {
if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(function(sign) {
return sign in env;
}) || env.CI_NAME === 'codeship') {
return 1;
}
return min;
}
if ('TEAMCITY_VERSION' in env) {
return (/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0
);
}
if ('TERM_PROGRAM' in env) {
var version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
switch (env.TERM_PROGRAM) {
case 'iTerm.app':
return version >= 3 ? 3 : 2;
case 'Hyper':
return 3;
case 'Apple_Terminal':
return 2;
// No default
}
}
if (/-256(color)?$/i.test(env.TERM)) {
return 2;
}
if (/^screen|^xterm|^vt100|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
return 1;
}
if ('COLORTERM' in env) {
return 1;
}
if (env.TERM === 'dumb') {
return min;
}
return min;
}
function getSupportLevel(stream) {
var level = supportsColor(stream);
return translateLevel(level);
}
module.exports = {
supportsColor: getSupportLevel,
stdout: getSupportLevel(process.stdout),
stderr: getSupportLevel(process.stderr),
};

1662
src/util/moment/format.js Normal file

File diff suppressed because it is too large Load Diff

129
src/util/moment/shorten.js Normal file
View File

@@ -0,0 +1,129 @@
if (typeof moment === "undefined" && typeof require === 'function') {
var moment = require('moment');
}
(function(moment) {
var STRINGS = {
nodiff: '',
year: 'year',
years: 'years',
month: 'month',
months: 'months',
day: 'day',
days: 'days',
hour: 'hour',
hours: 'hours',
minute: 'minute',
minutes: 'minutes',
second: 'second',
seconds: 'seconds',
delimiter: ' '
};
function pluralize(num, word) {
return num + ' ' + STRINGS[word + (num === 1 ? '' : 's')];
}
function buildStringFromValues(yDiff, mDiff, dDiff, hourDiff, minDiff, secDiff){
var result = [];
if (yDiff) {
result.push(pluralize(yDiff, 'year'));
}
if (mDiff) {
result.push(pluralize(mDiff, 'month'));
}
if (dDiff) {
result.push(pluralize(dDiff, 'day'));
}
if (hourDiff) {
result.push(pluralize(hourDiff, 'hour'));
}
if (minDiff) {
result.push(pluralize(minDiff, 'minute'));
}
if (secDiff) {
result.push(pluralize(secDiff, 'second'));
}
return result.join(STRINGS.delimiter);
}
function buildValueObject(yDiff, mDiff, dDiff, hourDiff, minDiff, secDiff, firstDateWasLater) {
return {
"years" : yDiff,
"months" : mDiff,
"days" : dDiff,
"hours" : hourDiff,
"minutes" : minDiff,
"seconds" : secDiff,
"firstDateWasLater" : firstDateWasLater
};
}
moment.fn.preciseDiff = function(d2, returnValueObject) {
return moment.preciseDiff(this, d2, returnValueObject);
};
moment.preciseDiff = function(d1, d2, returnValueObject) {
var m1 = moment(d1), m2 = moment(d2), firstDateWasLater;
m1.add(m2.utcOffset() - m1.utcOffset(), 'minutes'); // shift timezone of m1 to m2
if (m1.isSame(m2)) {
if (returnValueObject) {
return buildValueObject(0, 0, 0, 0, 0, 0, false);
} else {
return STRINGS.nodiff;
}
}
if (m1.isAfter(m2)) {
var tmp = m1;
m1 = m2;
m2 = tmp;
firstDateWasLater = true;
} else {
firstDateWasLater = false;
}
var yDiff = m2.year() - m1.year();
var mDiff = m2.month() - m1.month();
var dDiff = m2.date() - m1.date();
var hourDiff = m2.hour() - m1.hour();
var minDiff = m2.minute() - m1.minute();
var secDiff = m2.second() - m1.second();
if (secDiff < 0) {
secDiff = 60 + secDiff;
minDiff--;
}
if (minDiff < 0) {
minDiff = 60 + minDiff;
hourDiff--;
}
if (hourDiff < 0) {
hourDiff = 24 + hourDiff;
dDiff--;
}
if (dDiff < 0) {
var daysInLastFullMonth = moment(m2.year() + '-' + (m2.month() + 1), "YYYY-MM").subtract(1, 'M').daysInMonth();
if (daysInLastFullMonth < m1.date()) { // 31/01 -> 2/03
dDiff = daysInLastFullMonth + dDiff + (m1.date() - daysInLastFullMonth);
} else {
dDiff = daysInLastFullMonth + dDiff;
}
mDiff--;
}
if (mDiff < 0) {
mDiff = 12 + mDiff;
yDiff--;
}
if (returnValueObject) {
return buildValueObject(yDiff, mDiff, dDiff, hourDiff, minDiff, secDiff, firstDateWasLater);
} else {
return buildStringFromValues(yDiff, mDiff, dDiff, hourDiff, minDiff, secDiff);
}
};
}(moment));

94
src/util/normalize.js Normal file
View File

@@ -0,0 +1,94 @@
let changes;
const defaultDiacriticsRemovalMap = [
{ 'base': 'A', 'letters': /[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g},
{ 'base': 'AA', 'letters': /[\uA732]/g},
{ 'base': 'AE', 'letters': /[\u00C6\u01FC\u01E2]/g},
{ 'base': 'AO', 'letters': /[\uA734]/g},
{ 'base': 'AU', 'letters': /[\uA736]/g},
{ 'base': 'AV', 'letters': /[\uA738\uA73A]/g},
{ 'base': 'AY', 'letters': /[\uA73C]/g},
{ 'base': 'B', 'letters': /[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g},
{ 'base': 'C', 'letters': /[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g},
{ 'base': 'D', 'letters': /[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g},
{ 'base': 'DZ', 'letters': /[\u01F1\u01C4]/g},
{ 'base': 'Dz', 'letters': /[\u01F2\u01C5]/g},
{ 'base': 'E', 'letters': /[\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g},
{ 'base': 'F', 'letters': /[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g},
{ 'base': 'G', 'letters': /[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g},
{ 'base': 'H', 'letters': /[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g},
{ 'base': 'I', 'letters': /[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g},
{ 'base': 'J', 'letters': /[\u004A\u24BF\uFF2A\u0134\u0248]/g},
{ 'base': 'K', 'letters': /[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g},
{ 'base': 'L', 'letters': /[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g},
{ 'base': 'LJ', 'letters': /[\u01C7]/g},
{ 'base': 'Lj', 'letters': /[\u01C8]/g},
{ 'base': 'M', 'letters': /[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g},
{ 'base': 'N', 'letters': /[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g},
{ 'base': 'NJ', 'letters': /[\u01CA]/g},
{ 'base': 'Nj', 'letters': /[\u01CB]/g},
{ 'base': 'O', 'letters': /[\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g},
{ 'base': 'OI', 'letters': /[\u01A2]/g},
{ 'base': 'OO', 'letters': /[\uA74E]/g},
{ 'base': 'OU', 'letters': /[\u0222]/g},
{ 'base': 'P', 'letters': /[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g},
{ 'base': 'Q', 'letters': /[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g},
{ 'base': 'R', 'letters': /[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g},
{ 'base': 'S', 'letters': /[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g},
{ 'base': 'T', 'letters': /[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g},
{ 'base': 'TZ', 'letters': /[\uA728]/g},
{ 'base': 'U', 'letters': /[\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g},
{ 'base': 'V', 'letters': /[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g},
{ 'base': 'VY', 'letters': /[\uA760]/g},
{ 'base': 'W', 'letters': /[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g},
{ 'base': 'X', 'letters': /[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g},
{ 'base': 'Y', 'letters': /[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g},
{ 'base': 'Z', 'letters': /[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g},
{ 'base': 'a', 'letters': /[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g},
{ 'base': 'aa', 'letters': /[\uA733]/g},
{ 'base': 'ae', 'letters': /[\u00E6\u01FD\u01E3]/g},
{ 'base': 'ao', 'letters': /[\uA735]/g},
{ 'base': 'au', 'letters': /[\uA737]/g},
{ 'base': 'av', 'letters': /[\uA739\uA73B]/g},
{ 'base': 'ay', 'letters': /[\uA73D]/g},
{ 'base': 'b', 'letters': /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g},
{ 'base': 'c', 'letters': /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g},
{ 'base': 'd', 'letters': /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g},
{ 'base': 'dz', 'letters': /[\u01F3\u01C6]/g},
{ 'base': 'e', 'letters': /[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g},
{ 'base': 'f', 'letters': /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g},
{ 'base': 'g', 'letters': /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g},
{ 'base': 'h', 'letters': /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g},
{ 'base': 'hv', 'letters': /[\u0195]/g},
{ 'base': 'i', 'letters': /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g},
{ 'base': 'j', 'letters': /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g},
{ 'base': 'k', 'letters': /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g},
{ 'base': 'l', 'letters': /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g},
{ 'base': 'lj', 'letters': /[\u01C9]/g},
{ 'base': 'm', 'letters': /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g},
{ 'base': 'n', 'letters': /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g},
{ 'base': 'nj', 'letters': /[\u01CC]/g},
{ 'base': 'o', 'letters': /[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g},
{ 'base': 'oi', 'letters': /[\u01A3]/g},
{ 'base': 'ou', 'letters': /[\u0223]/g},
{ 'base': 'oo', 'letters': /[\uA74F]/g},
{ 'base': 'p', 'letters': /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g},
{ 'base': 'q', 'letters': /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g},
{ 'base': 'r', 'letters': /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g},
{ 'base': 's', 'letters': /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g},
{ 'base': 't', 'letters': /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g},
{ 'base': 'tz', 'letters': /[\uA729]/g},
{ 'base': 'u', 'letters': /[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g},
{ 'base': 'v', 'letters': /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g},
{ 'base': 'vy', 'letters': /[\uA761]/g},
{ 'base': 'w', 'letters': /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g},
{ 'base': 'x', 'letters': /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g},
{ 'base': 'y', 'letters': /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g},
{ 'base': 'z', 'letters': /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g}
];
module.exports = (str) => {
if ( !changes ) changes = defaultDiacriticsRemovalMap;
for ( let i = 0; i < changes.length; i++ ) { str = str.replace( changes[i].letters, changes[i].base ); }
return str;
};

22
src/util/raspi.js Normal file
View File

@@ -0,0 +1,22 @@
const { readFileSync } = require('fs');
const MODELS = [ 'BCM2708', 'BCM2709', 'BCM2710', 'BCM2835', 'BCM2837B0' ];
const isPi = ( model ) => { return MODELS.indexOf(model) > -1; };
module.exports = () => {
let cpuInfo;
try { cpuInfo = readFileSync('/proc/cpuinfo', { encoding: 'utf8' }); }
catch (ex) { return false; }
const model = cpuInfo
.split('\n')
.map((v) => v.replace(/\t/g, ''))
.filter((v) => v.length > 0)
.map((v) => v.split( ':' ) )
.map((v) => v.map((e) => e.trim()))
.filter((v) => v[0] === 'Hardware');
if (!model || model.length <= 0) return false;
return isPi(model[0][1]);
};

1017
yarn.lock Normal file

File diff suppressed because it is too large Load Diff