Compare commits
135 commits
Author | SHA1 | Date | |
---|---|---|---|
Alexander Yakovlev | e2f36f65f4 | ||
9940e04431 | |||
7adcdb89cd | |||
792b62fc73 | |||
70f078c4b2 | |||
5122eafc74 | |||
6a140afbe8 | |||
4b815523f3 | |||
d678f9db38 | |||
e514f7af91 | |||
6a9a725cac | |||
5e0250731c | |||
78960db384 | |||
f378a3a47b | |||
ee938ff563 | |||
5f2b3af2d7 | |||
19677b7626 | |||
17eb0554fe | |||
f1f81a9ba4 | |||
672c07f5b0 | |||
381ff1d219 | |||
1e0d8d3266 | |||
71c77d41a3 | |||
f7c00f898c | |||
559a61828d | |||
1a60292860 | |||
1d5ba2561b | |||
b77eded7c2 | |||
f37c114624 | |||
120e1f66d8 | |||
b407f66c88 | |||
7a60083945 | |||
309286b528 | |||
f996eb363b | |||
3cdf6b2b2a | |||
91d200eb44 | |||
1150911a93 | |||
25f67948b2 | |||
7b7645cb5e | |||
d452569ad1 | |||
fbf762ff16 | |||
7ee7d4c704 | |||
021abe31f7 | |||
2ff31f8ec2 | |||
adab6692e7 | |||
84cbe10eba | |||
79ce4d2501 | |||
4afadaaa26 | |||
a0e94ae86d | |||
5f7fb483da | |||
d97468ebd2 | |||
e953e1cdca | |||
2b921c84e9 | |||
784ef72392 | |||
446df2f8a7 | |||
71f0e344e1 | |||
cafecb4e13 | |||
94c16927d3 | |||
5b054b520b | |||
c48c443854 | |||
6013b0dcbf | |||
c93fe0d6ce | |||
fe9781bdfe | |||
b2c849e8c4 | |||
834898a2f2 | |||
30ea6239b9 | |||
b1ac656bee | |||
b6ac3b2518 | |||
18f300d46d | |||
987473ffa6 | |||
96def786a4 | |||
80f49e5564 | |||
c92688c80a | |||
73d40eafc0 | |||
663ef8ccba | |||
13267db0d2 | |||
538732cbf4 | |||
db86cacaa0 | |||
21c51fbc3d | |||
d2fa55beae | |||
f2313c7da4 | |||
723bbb1098 | |||
f3c3e57e41 | |||
2e985ad5fa | |||
8a71425dc4 | |||
c151201384 | |||
446292b057 | |||
1cb65ade49 | |||
e29283af1c | |||
20bc117dae | |||
f6d1b5f7a6 | |||
caf664e014 | |||
90619bef9a | |||
607a3804bc | |||
0047f31770 | |||
e733a8b481 | |||
e882ffa82e | |||
a0571cfc11 | |||
b65d9e89cf | |||
9d4aad4cf9 | |||
d348d7e41f | |||
7c5ef008d8 | |||
53fc953caa | |||
3e268d2231 | |||
8980dad584 | |||
3beeea1588 | |||
6343ae50ea | |||
f4e39a69dc | |||
f193393a2a | |||
b4eb3d3551 | |||
1b2e06c2d5 | |||
b777046b0b | |||
328623b8dd | |||
3fc93e06cd | |||
f00b7ec876 | |||
8989daef49 | |||
1fc45e85b4 | |||
e249b8d45d | |||
4dc73a6783 | |||
a5371c4711 | |||
07ca4336fb | |||
a19c626753 | |||
4ba9f21e69 | |||
d8795740f9 | |||
35ce6a56be | |||
e447c1c07f | |||
691af2761f | |||
347aedc4ab | |||
aeba653e9f | |||
2b0750dada | |||
0601c29c85 | |||
1cfc7a19c3 | |||
4332c66b43 | |||
b6cc8298bc | |||
2a7cd67506 |
35
.eslintrc
Normal file
35
.eslintrc
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"commonjs": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"warn",
|
||||
4,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"quotes": [
|
||||
"warn",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-undef": "error",
|
||||
"no-unused-vars": "warn",
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"always"
|
||||
]
|
||||
}
|
||||
}
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,8 +1,6 @@
|
|||
### Discord bots ####
|
||||
guilds.json
|
||||
token.json
|
||||
log
|
||||
|
||||
update.sh
|
||||
guilds-data/*
|
||||
token.json*
|
||||
|
||||
# Created by https://www.gitignore.io/api/node,visualstudiocode
|
||||
|
||||
|
|
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
|
@ -8,7 +8,11 @@
|
|||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"program": "${workspaceRoot}/bootstrap.js"
|
||||
"program": "${workspaceRoot}/app/index.js",
|
||||
"args":[
|
||||
"token.json",
|
||||
"guilds.json"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"files.exclude": {
|
||||
"log": true,
|
||||
".npmrc": true,
|
||||
"node_modules": true,
|
||||
"node_cache": true,
|
||||
"token.json": true
|
||||
}
|
||||
}
|
139
CHANGELOG.md
139
CHANGELOG.md
|
@ -1,22 +1,119 @@
|
|||
# Changelog
|
||||
|
||||
## v3.0.0-b2
|
||||
## v3.5.2
|
||||
### Added
|
||||
- Added bot name to version command
|
||||
- Added stats command
|
||||
|
||||
### Fixed
|
||||
- Fixed reset command not being admin only
|
||||
|
||||
- Full and short youtube urls not being properly converted
|
||||
|
||||
## v3.0.0-b1
|
||||
|
||||
### Added
|
||||
- Fancy new @bot help command
|
||||
## v3.5.1
|
||||
### Fixed
|
||||
- Fixed reset command not working
|
||||
|
||||
### Updated
|
||||
- Removed deletion of data for removed guilds on startup, as Discord outages can wrongly report the bot as being removed
|
||||
|
||||
## v3.5.0
|
||||
### Added
|
||||
- MongoDB support
|
||||
- Reset command to clear all data for a guild
|
||||
|
||||
### Updated
|
||||
- Removed automatic daily restart (I think it shouldn't be needed now that MongoDB support is working)
|
||||
|
||||
### Fixed
|
||||
- Guild data being deleted on a Discord outage
|
||||
|
||||
## v3.4.0
|
||||
### Added
|
||||
- Added automatic daily restart
|
||||
|
||||
### Updated
|
||||
- Disabled a few unused websocket events
|
||||
- Update database compaction to be more frequent
|
||||
|
||||
### Fixed
|
||||
- Fixed YouTube feeds not updating
|
||||
|
||||
## v3.3.0
|
||||
### Updated
|
||||
- Updated RSS parser to now use [rss-parser](https://www.npmjs.com/package/rss-parser) module to increase compatibility with feeds
|
||||
- Updated RSS entry caching to exclude 'www.' prefix to avoid certain mis-caches
|
||||
|
||||
## v3.2.2
|
||||
### Fixed
|
||||
- Fixed a couple of edge case errors
|
||||
|
||||
## v3.2.1
|
||||
### Fixed
|
||||
- Fixed memory leak due to unconfigured discord.js caching
|
||||
|
||||
## v3.2.0
|
||||
### Updated
|
||||
- Updated data storage to use a NeDB database rather than a json file
|
||||
- Updated feed checking interval to check one guild every 10 seconds (this may slow down the time it takes to post, but will improve performance)
|
||||
- Improve stability of feed checking
|
||||
- Tidy up some console spam
|
||||
### Fixed
|
||||
- Fix bot crash if feed article contains link with invalid host name
|
||||
- Temporary fix for bot crash if used with a feed without links in the articles (didn't realise this was possible...)
|
||||
- Attempt fix for issues caused by every feed of every guild being checked at the same time
|
||||
|
||||
## v3.1.3
|
||||
### Added
|
||||
- Add rudimentary pagination for viewing feeds when there are more than 10
|
||||
|
||||
### Fixed
|
||||
- Fix articles not posting if contents too long for a single discord message
|
||||
|
||||
## v3.1.2
|
||||
### Fixed
|
||||
- Deleted channels with feeds sending the bot into a reconnect loop
|
||||
|
||||
## v3.1.1
|
||||
### Fixed
|
||||
- Empty RSS feed crash
|
||||
|
||||
## v3.1.0
|
||||
### Added
|
||||
- RSS element content is now included in the post the bot makes when there is a new feed
|
||||
- Warning message after setup command if supplied URL does not return valid RSS
|
||||
- Guild join and leave messages in the console
|
||||
- Removal of guild data if the bot leaves a guild
|
||||
|
||||
### Updated
|
||||
- Route a lot of mostly irrelevant console spam to a file instead of the console
|
||||
- Updated launch command to pass max-old-space-size parameter to limit memory usage
|
||||
|
||||
### Fixed
|
||||
- Fixed syntax error when role omitted in feed setup command; it is now properly optional
|
||||
- Fixed the wrong feed sometimes being removed when using the remove-feed command
|
||||
|
||||
## v3.0.1
|
||||
### Fixed
|
||||
- Fixed nicknamed bot not responding to users on android
|
||||
- Fixed "playing" message including "https://" in front of site url
|
||||
|
||||
## v3.0.0
|
||||
### Added
|
||||
- Significantly more debug logging
|
||||
- Fancy new @bot help command
|
||||
|
||||
### Updatd
|
||||
- Significant back-end updates
|
||||
- Commands now invoked with an @mention to the bot
|
||||
- Updated error handling for Discord API errors
|
||||
- Removed "Body is not RSS or ATOM" error from being console logged
|
||||
- These seem to happen quite a lot, but don't actually impair the functionality, so just cause un-necessary spam
|
||||
- Removed "command not recognised" response, it caused 'fake' errors if multiple bots being run off the same token
|
||||
|
||||
### Fixed
|
||||
- Fixed full and short youtube urls not being properly converted
|
||||
- Fixed "multiple instance" issue
|
||||
- Fixed a couple of occasional memory leaks
|
||||
|
||||
## v2.0.0-b1
|
||||
|
||||
### Added
|
||||
- Multi-guild support
|
||||
- In-chat commands for setup and configuration
|
||||
|
@ -25,47 +122,35 @@
|
|||
- Remove an existing feed
|
||||
|
||||
### Updated
|
||||
|
||||
- Make save file configurable to allow use as a module with other bots
|
||||
- Update config file structure
|
||||
- Now uses discord.js instead of discord.io
|
||||
- YouTube links automatically handled; no more separate "YouTube mode" config item
|
||||
|
||||
### Fixed
|
||||
|
||||
- Crash if trying to view feeds list before any feeds have been set up
|
||||
|
||||
## v1.4.0
|
||||
|
||||
### Added
|
||||
|
||||
- Support for posting links from multiple feeds
|
||||
- Tagging of separate roles for each feed being checked
|
||||
|
||||
### Updated
|
||||
|
||||
- Updated bot connection code to use my discord-bot-wrapper
|
||||
|
||||
### Removed
|
||||
|
||||
- !logsplease command removed as the OTT logging was just being annoying
|
||||
|
||||
## v1.3.2
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed list posting channel messages being ignored
|
||||
|
||||
## v1.3.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Developer commands can now be used from any channel or PM
|
||||
|
||||
## v1.3.0
|
||||
|
||||
### Added
|
||||
|
||||
- Deletion of "You have successfully subscribed" messages after a short delay (configurable)
|
||||
- 'Developer' commands that can only be accessed by specified users
|
||||
- !cacheList developer command to view the cached URLs
|
||||
|
@ -78,44 +163,34 @@
|
|||
- The role is mentioned when the link is posted, rather than a long chain of user IDs
|
||||
|
||||
## v1.2.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed multiple users being unsubscribed when one user unsubscribes
|
||||
|
||||
## v1.2.0
|
||||
|
||||
### Added
|
||||
|
||||
- Chat message/command to request a list of subscribed users
|
||||
- The ability for users to 'subscribe' so they are tagged whenever a new link is posted
|
||||
- Logging to a file
|
||||
- Ability for user to request an upload of the logs file
|
||||
|
||||
### Updated
|
||||
|
||||
- Added basic spam reduction when logging so the same message won't get logged multiple times in a row
|
||||
- Refactored a bunch of code to improve efficiency
|
||||
- Updated timer logic to only ever use a single timer, and share it between posting and reconnecting
|
||||
|
||||
## v1.1.2
|
||||
|
||||
### Updated
|
||||
- Updated reconnect logic to hopefully be more stable
|
||||
|
||||
## v1.1.1
|
||||
|
||||
### Added
|
||||
- Reconnect timer to repeatedly try reconnect at intervals
|
||||
|
||||
### Updated
|
||||
|
||||
- Updated support for https conversion to http to hopefully be more consistent
|
||||
|
||||
## v1.1.0
|
||||
|
||||
### Added
|
||||
|
||||
- Added togglable YouTube mode
|
||||
- Converts full URLs to YouTube share URLs
|
||||
- Checks against both YouTube full and share URLs to ensure same video not posted twice
|
||||
|
@ -126,4 +201,4 @@
|
|||
- Changed expected name for bot config file to bot-config.json rather than botConfig.json
|
||||
|
||||
### Fixed
|
||||
- New timer being created every time the bot reconnected
|
||||
- New timer being created every time the bot reconnected
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Benjamin Higgins
|
||||
|
||||
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.
|
33
README.md
33
README.md
|
@ -1,4 +1,4 @@
|
|||
# Discord rss bot
|
||||
# Discord RSS Bot
|
||||
|
||||
<!--summary-->
|
||||
Posts the latest URLs from an RSS feed, optionally @mention-ing a role when posted
|
||||
|
@ -18,46 +18,48 @@ Posts the latest URLs from an RSS feed, optionally @mention-ing a role when post
|
|||
## Invite
|
||||
|
||||
By inviting this bot to your server you agree to the [terms and conditions](#privacy-statement) laid out in the privacy section of this document.
|
||||
If you agree, invite to your server with [this link](https://discordapp.com/oauth2/authorize?client_id=343909688045469698&scope=bot&permissions=0x00010c00).
|
||||
~~If you agree, invite to your server with this link~~.
|
||||
*Temporarily unavailable due to scaling issues and the large amount of attention recently.*
|
||||
|
||||
## Setup
|
||||
|
||||
You can ask the bot for help with commands by typing `@RSS_Bot help`
|
||||
You can ask the bot for help with commands by typing `@RSS Bot help`
|
||||
|
||||
### Add a new feed
|
||||
|
||||
`@RSS_Bot add-feed <url> <#channel> [@role]`
|
||||
`@RSS Bot add-feed <url> <#channel> [@role]`
|
||||
- *url* must be an RSS feed URL
|
||||
- *#channel* must be a channel mention
|
||||
- *@role* must be a role mention (make sure "Anyone can mention this role" is turned on during setup)
|
||||
|
||||
Example:
|
||||
`@RSS_Bot add-feed http://lorem-rss.herokuapp.com/feed?unit=second&interval=30 #rss-posts @subscribers`
|
||||
`@RSS Bot add-feed http://lorem-rss.herokuapp.com/feed?unit=second&interval=30 #rss-posts @subscribers`
|
||||
|
||||
### View feeds configured for this server
|
||||
|
||||
`@RSS_Bot view-feeds`
|
||||
`@RSS Bot view-feeds`
|
||||
This will display a list of RSS feeds configured for this server, along with a unique ID for each
|
||||
|
||||
### Remove a configured feed
|
||||
|
||||
`@RSS_Bot remove-feed <feed-id>`
|
||||
`@RSS Bot remove-feed <feed-id>`
|
||||
|
||||
To remove a feed you will need it's unique ID, which you can find by running the above *view-feeds* command
|
||||
|
||||
Example:
|
||||
`@RSS_Bot remove-feed ABc-123dEF`
|
||||
`@RSS Bot remove-feed ABc-123dEF`
|
||||
|
||||
## Permissions
|
||||
|
||||
The bot requires certain permissions, which you are prompted for on the invite screen.
|
||||
Each permission has a reason for being required, explained below.
|
||||
|
||||
| Permission | Reason |
|
||||
|----------------------|-------------------------------------------------------------|
|
||||
| Read messages | Detect when you use commands |
|
||||
| Send messages | Respond when you use commands; post new RSS links |
|
||||
| Read message history | Check if any new RSS links have been posted during downtime |
|
||||
| Permission | Reason |
|
||||
|----------------------|--------------------------------------------------------------|
|
||||
| Read messages | Detect when you use commands |
|
||||
| Send messages | Respond when you use commands; post new RSS links |
|
||||
| Read message history | Check if any new RSS links have been posted during downtime |
|
||||
| Embed links | Responses to 'help' requests use message embeds for nice formatting |
|
||||
|
||||
## Privacy statement
|
||||
|
||||
|
@ -78,9 +80,12 @@ Should you wish for the data stored about your server to be removed, please cont
|
|||
1. Clone the repository, or download and extract the zip file (preferrably from the release page)
|
||||
2. Make sure you have *npm* and *git* installed
|
||||
3. Run `npm install`
|
||||
4. Add *token.json* in the root folder: `{ "token": "your-token-goes-here" }`
|
||||
4. Add *token.json* in the root folder (make sure to include the quotes ""): `"your-token-goes-here"`
|
||||
5. Run `npm start`
|
||||
|
||||
**Note for git users**
|
||||
If you cloned the repository with git, make sure you `git reset --hard vX.Y` to a specific version, as latest master isn't always production ready!
|
||||
|
||||
## Need help?
|
||||
|
||||
I am available for contact via my [support Discord server](https://discordapp.com/invite/SSkbwSJ). I will always do my best to respond, however I am often busy so can't always be available right away, and as this is a free service I may not always be able to resolve your query.
|
||||
|
|
108
app/bot.js
108
app/bot.js
|
@ -1,108 +0,0 @@
|
|||
//external lib imports
|
||||
const GetUrls = require("get-urls"); //for extracting urls from messages
|
||||
|
||||
//my imports
|
||||
const DiscordUtil = require("discordjs-util");
|
||||
|
||||
//app component imports
|
||||
const GuildData = require("./models/guild-data.js");
|
||||
const FeedData = require("./models/feed-data.js");
|
||||
|
||||
module.exports = {
|
||||
onReady(client, guildsData, config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
parseLinksInGuilds(client.guilds, guildsData)
|
||||
.then(() => checkFeedsInGuilds(client.guilds, guildsData))
|
||||
.then(() => setInterval(() => checkFeedsInGuilds(client.guilds, guildsData), config.feedCheckIntervalSec * 1000)); //set up an interval to check all the feeds
|
||||
});
|
||||
},
|
||||
onCommand(commandObj, commandsObj, params, guildData, message, config, client, botName) {
|
||||
switch (commandObj.command) {
|
||||
case commandsObj.addFeed.command:
|
||||
return addFeed(client, guildData, message, config.maxCacheSize);
|
||||
case commandsObj.removeFeed.command:
|
||||
return removeFeed(guildData, message, botName);
|
||||
case commandsObj.viewFeeds.command:
|
||||
return viewFeeds(guildData);
|
||||
}
|
||||
},
|
||||
onNonCommandMsg(message, guildData) {
|
||||
guildData.feeds.forEach(feedData => {
|
||||
if (message.channel.name === feedData.channelName)
|
||||
feedData.cachedLinks.push(...GetUrls(message.content)); //spread the urlSet returned by GetUrls into the cache array
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function addFeed(client, guildData, message, maxCacheSize) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const feedUrl = [...GetUrls(message.content)][0];
|
||||
const channel = message.mentions.channels.first();
|
||||
|
||||
if (!feedUrl || !channel)
|
||||
reject("Please provide both a channel and an RSS feed URL. You can optionally @mention a role also.");
|
||||
|
||||
const role = message.mentions.roles.first();
|
||||
|
||||
const feedData = new FeedData({
|
||||
url: feedUrl,
|
||||
channelName: channel.name,
|
||||
roleName: role ? role.name : null,
|
||||
maxCacheSize: maxCacheSize
|
||||
});
|
||||
|
||||
//ask the user if they're happy with the details they set up, save if yes, don't if no
|
||||
DiscordUtil.ask(client, message.channel, message.member, "Are you happy with this?\n" + feedData.toString())
|
||||
.then(responseMessage => {
|
||||
|
||||
//if they responded yes, save the feed and let them know, else tell them to start again
|
||||
if (responseMessage.content.toLowerCase() === "yes") {
|
||||
if (!guildData)
|
||||
guildData = new GuildData({ id: message.guild.id, feeds: [] });
|
||||
|
||||
guildData.feeds.push(feedData);
|
||||
resolve("Your new feed has been saved!");
|
||||
}
|
||||
else
|
||||
reject("Your feed has not been saved, please add it again with the correct details");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function removeFeed(guildData, message, botName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const parameters = message.content.split(" ");
|
||||
if (parameters.length !== 3)
|
||||
resolve(`Please use the command as such:\n\`\`\` ${botName} remove-feed feedid\`\`\``);
|
||||
else {
|
||||
const idx = guildData.feeds.findIndex(feed => feed.id === parameters[2]);
|
||||
if (!Number.isInteger(idx))
|
||||
reject("Can't find feed with id " + parameters[2]);
|
||||
else {
|
||||
guildData.feeds.splice(idx, 1);
|
||||
resolve("Feed removed!");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function viewFeeds(guildData) {
|
||||
if (!guildData)
|
||||
return Promise.reject("Guild not setup");
|
||||
|
||||
return Promise.resolve(guildData.feeds.map(f => f.toString()).join("\n"));
|
||||
}
|
||||
|
||||
function checkFeedsInGuilds(guilds, guildsData) {
|
||||
Object.keys(guildsData).forEach(key => guildsData[key].checkFeeds(guilds));
|
||||
}
|
||||
|
||||
function parseLinksInGuilds(guilds, guildsData) {
|
||||
const promises = [];
|
||||
for (let guildId of guilds.keys()) {
|
||||
const guildData = guildsData[guildId];
|
||||
if (guildData)
|
||||
promises.push(guildData.cachePastPostedLinks(guilds.get(guildId)));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
51
app/commands/add-feed.js
Normal file
51
app/commands/add-feed.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
const { promisify } = require("util");
|
||||
const Config = require("../config.json");
|
||||
const Core = require("../../core");
|
||||
const FeedData = require("../models/feed-data.js");
|
||||
const GetUrls = require("get-urls");
|
||||
const ShortID = require("shortid");
|
||||
|
||||
// @ts-ignore
|
||||
const readFeed = url => promisify(require("rss-parser").parseURL)(url);
|
||||
|
||||
module.exports = new Core.Command({
|
||||
name: "add-feed",
|
||||
description: "Add an RSS feed to be posted in a channel, with an optional role to tag",
|
||||
syntax: "add-feed <url> <#channel> [@role]",
|
||||
admin: true,
|
||||
invoke: invoke
|
||||
});
|
||||
|
||||
function invoke({ message, params, guildData, client }) {
|
||||
const feedUrl = [...GetUrls(message.content)][0],
|
||||
channel = message.mentions.channels.first();
|
||||
|
||||
if (!feedUrl || !channel)
|
||||
return Promise.reject("Please provide both a channel and an RSS feed URL. You can optionally @mention a role also.");
|
||||
|
||||
const role = message.mentions.roles.first(),
|
||||
feedData = FeedData.create({
|
||||
feedID: ShortID.generate(),
|
||||
url: feedUrl,
|
||||
channelID: channel.id,
|
||||
roleID: role ? role.id : null,
|
||||
maxCacheSize: Config.maxCacheSize
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
readFeed(feedUrl)
|
||||
.then(() => {
|
||||
Core.util.ask(client, message.channel, message.member, "Are you happy with this (yes/no)?\n" + feedData.toString())
|
||||
.then(responseMessage => {
|
||||
if (responseMessage.content.toLowerCase() === "yes") {
|
||||
guildData.feeds.push(feedData);
|
||||
guildData.cachePastPostedLinks(message.guild)
|
||||
.then(() => resolve("Your new feed has been saved!"));
|
||||
}
|
||||
else
|
||||
reject("Your feed has not been saved, please add it again with the correct details");
|
||||
});
|
||||
})
|
||||
.catch(err => reject(`Unable to add the feed due to the following error:\n${err.message}`));
|
||||
});
|
||||
}
|
18
app/commands/remove-feed.js
Normal file
18
app/commands/remove-feed.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
const Core = require("../../core");
|
||||
|
||||
module.exports = new Core.Command({
|
||||
name: "remove-feed",
|
||||
description: "Remove an RSS feed by it's ID",
|
||||
syntax: "remove-feed <dir>",
|
||||
admin: true,
|
||||
invoke: invoke
|
||||
});
|
||||
|
||||
function invoke({ message, params, guildData, client }) {
|
||||
const idx = guildData.feeds.findIndex(feed => feed.feedID === params[0]);
|
||||
if (!Number.isInteger(idx))
|
||||
return Promise.reject("Can't find feed with id " + params[0]);
|
||||
|
||||
guildData.feeds.splice(idx, 1);
|
||||
return Promise.resolve("Feed removed!");
|
||||
}
|
23
app/commands/view-feeds.js
Normal file
23
app/commands/view-feeds.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
const Core = require("../../core");
|
||||
const Config = require("../config.json");
|
||||
|
||||
module.exports = new Core.Command({
|
||||
name: "view-feeds",
|
||||
description: "View a list of configured feeds and their associated details",
|
||||
syntax: "view-feed",
|
||||
admin: true,
|
||||
invoke: invoke
|
||||
});
|
||||
|
||||
function invoke({ message, params, guildData, client }) {
|
||||
if (!guildData)
|
||||
return Promise.reject("Guild not setup");
|
||||
|
||||
const startIdx = params[0] ? (params[0] - 1) * Config.viewFeedsPaginationLimit : 0;
|
||||
const endIdx = startIdx + Config.viewFeedsPaginationLimit + 1;
|
||||
|
||||
let responseStr = guildData.feeds.map(f => f.toString()).slice(startIdx, endIdx).join("\n");
|
||||
if (guildData.feeds.length > endIdx)
|
||||
responseStr += `Use *view-feeds ${startIdx + 2}* to view more`;
|
||||
return Promise.resolve(responseStr || "No feeds configured");
|
||||
}
|
|
@ -1,43 +1,6 @@
|
|||
{
|
||||
"generic": {
|
||||
"saveFile": "./guilds.json",
|
||||
"saveIntervalSec": 60,
|
||||
"website": "https://benji7425.github.io",
|
||||
"discordInvite": "https://discord.gg/SSkbwSJ",
|
||||
"defaultDMResponse": "This bot does not have any handling for direct messages. To learn more or get help please visit %s, or join my Discord server here: %s"
|
||||
},
|
||||
"maxCacheSize": 100,
|
||||
"feedCheckIntervalSec": 30,
|
||||
"commands": {
|
||||
"version": {
|
||||
"command": "version",
|
||||
"description": "Returns the bot version",
|
||||
"syntax": "version",
|
||||
"admin": false
|
||||
},
|
||||
"help": {
|
||||
"command": "help",
|
||||
"description": "Display information about commands available to you",
|
||||
"syntax": "help",
|
||||
"admin": false
|
||||
},
|
||||
"addFeed": {
|
||||
"command": "add-feed",
|
||||
"description": "Add an RSS feed to be posted in a channel, with an optional role to tag",
|
||||
"syntax": "add-feed <url> <#channel> [@role]",
|
||||
"admin": true
|
||||
},
|
||||
"removeFeed": {
|
||||
"command": "remove-feed",
|
||||
"description": "Remove an RSS feed by it's ID",
|
||||
"syntax": "remove-feed <id>",
|
||||
"admin": true
|
||||
},
|
||||
"viewFeeds": {
|
||||
"command": "view-feeds",
|
||||
"description": "View a list of configured feeds and their associated details",
|
||||
"syntax": "view-feed",
|
||||
"admin": true
|
||||
}
|
||||
}
|
||||
"feedCheckInterval": 10000,
|
||||
"charLimit": 500,
|
||||
"viewFeedsPaginationLimit": 10
|
||||
}
|
163
app/index.js
163
app/index.js
|
@ -1,123 +1,74 @@
|
|||
//node imports
|
||||
const FileSystem = require("fs"); //manage files
|
||||
const Util = require("util"); //various node utilities
|
||||
const Core = require("../core");
|
||||
const GetUrls = require("get-urls");
|
||||
const GuildData = require("./models/guild-data.js");
|
||||
// @ts-ignore
|
||||
const Config = require("./config.json");
|
||||
|
||||
//external lib imports
|
||||
const Discord = require("discord.js");
|
||||
const JsonFile = require("jsonfile"); //save/load data to/from json
|
||||
const guildsIterator = (function* () {
|
||||
while (true) {
|
||||
if (client.guilds.size === 0)
|
||||
yield null;
|
||||
else
|
||||
for (let i = 0; i < client.guilds.size; i++)
|
||||
yield [...client.guilds.values()][i];
|
||||
}
|
||||
})();
|
||||
|
||||
//my imports
|
||||
const DiscordUtil = require("discordjs-util"); //some discordjs helper functions of mine
|
||||
// @ts-ignore
|
||||
const client = new Core.Client(require("../token.json"), __dirname + "/commands", GuildData);
|
||||
|
||||
//app components
|
||||
const GuildData = require("./models/guild-data.js"); //data structure for guilds
|
||||
const PackageJSON = require("../package.json"); //used to provide some info about the bot
|
||||
const Bot = require("./bot.js");
|
||||
client.on("beforeLogin", () =>
|
||||
setInterval(doGuildIteration, Config.feedCheckInterval));
|
||||
|
||||
//global vars
|
||||
let writeFile = null;
|
||||
client.on("ready", () => {
|
||||
parseLinksInGuilds().then(doGuildIteration);
|
||||
require("./legacy-upgrader.js")(); //upgrade legacy json into new database format
|
||||
});
|
||||
|
||||
//use module.exports as a psuedo "onready" function
|
||||
module.exports = (client, config = null) => {
|
||||
config = config || require("./config.json"); //load config file
|
||||
const guildsData = FileSystem.existsSync(config.generic.saveFile) ? fromJSON(JsonFile.readFileSync(config.generic.saveFile)) : {}; //read data from file, or generate new one if file doesn't exist
|
||||
client.on("message", message => {
|
||||
if (message.channel.type !== "text" || !message.member)
|
||||
return;
|
||||
|
||||
//create our writeFile function that will allow other functions to save data to json without needing access to the full guildsData or config objects
|
||||
//then set an interval to automatically save data to file
|
||||
writeFile = () => JsonFile.writeFile(config.generic.saveFile, guildsData, err => { if (err) DiscordUtil.dateError("Error writing file", err); });
|
||||
setInterval(() => writeFile(), config.generic.saveIntervalSec * 1000);
|
||||
client.guildDataModel.findOne({ guildID: message.guild.id })
|
||||
.then(guildData => guildData && cacheUrlsInMessage(message, guildData));
|
||||
});
|
||||
|
||||
//handle messages
|
||||
client.on("message", message => {
|
||||
if (message.author.id !== client.user.id) { //check the bot isn't triggering itself
|
||||
client.bootstrap();
|
||||
|
||||
//check whether we need to use DM or text channel handling
|
||||
if (message.channel.type === "dm")
|
||||
HandleMessage.dm(client, config, message);
|
||||
else if (message.channel.type === "text" && message.member)
|
||||
HandleMessage.text(client, config, message, guildsData);
|
||||
}
|
||||
});
|
||||
//INTERNAL FUNCTIONS//
|
||||
function parseLinksInGuilds() {
|
||||
const promises = [];
|
||||
|
||||
Bot.onReady(client, guildsData, config).then(() => writeFile).catch(err => DiscordUtil.dateError(err));
|
||||
};
|
||||
client.guildDataModel.find().then(guildDatas =>
|
||||
guildDatas
|
||||
.filter(guildData => client.guilds.get(guildData.guildID))
|
||||
.map(guildData => ({ guildData, guild: client.guilds.get(guildData.guildID) }))
|
||||
.forEach(({ guildData, guild }) => promises.push(guildData.cachePastPostedLinks(guild).catch()))
|
||||
);
|
||||
|
||||
const HandleMessage = {
|
||||
dm: (client, config, message) => {
|
||||
message.reply(Util.format(config.generic.defaultDMResponse, config.generic.website, config.generic.discordInvite));
|
||||
},
|
||||
text: (client, config, message, guildsData) => {
|
||||
const isCommand = message.content.startsWith(message.guild.me.toString());
|
||||
let guildData = guildsData[message.guild.id];
|
||||
|
||||
if (!guildData)
|
||||
guildData = guildsData[message.guild.id] = new GuildData({ id: message.guild.id });
|
||||
|
||||
if (isCommand) {
|
||||
const userIsAdmin = message.member.permissions.has("ADMINISTRATOR");
|
||||
const botName = "@" + (message.guild.me.nickname || client.user.username);
|
||||
|
||||
const split = message.content.toLowerCase().split(/\ +/); //split the message at whitespace
|
||||
const command = split[1]; //extract the command used
|
||||
const commandObj = config.commands[Object.keys(config.commands).find(x => config.commands[x].command.toLowerCase() === command)]; //find the matching command object
|
||||
|
||||
if (!commandObj || (!commandObj.admin && !userIsAdmin))
|
||||
return;
|
||||
|
||||
const params = split.slice(2, split.length); //extract the parameters passed for the command
|
||||
const expectedParamCount = commandObj.syntax.split(/\ +/).length - 1; //calculate the number of expected command params
|
||||
|
||||
let finalisedParams;
|
||||
if (params.length > expectedParamCount) //if we have more params than needed
|
||||
finalisedParams = params.slice(0, expectedParamCount - 1).concat([params.slice(expectedParamCount - 1, params.length).join(" ")]);
|
||||
else //else we either have exactly the right amount, or not enough
|
||||
finalisedParams = params;
|
||||
|
||||
//find which command was used and handle it
|
||||
switch (command) {
|
||||
case config.commands.version.command:
|
||||
message.reply(`${PackageJSON.name} v${PackageJSON.version}`);
|
||||
break;
|
||||
case config.commands.help.command:
|
||||
message.channel.send(createHelpEmbed(botName, config, userIsAdmin));
|
||||
break;
|
||||
default:
|
||||
if (finalisedParams.length >= expectedParamCount)
|
||||
Bot.onCommand(commandObj, config.commands, finalisedParams, guildData, message, config, client, botName)
|
||||
.then(msg => {
|
||||
message.reply(msg);
|
||||
writeFile();
|
||||
})
|
||||
.catch(err => {
|
||||
message.reply(err);
|
||||
DiscordUtil.dateError(err);
|
||||
});
|
||||
else
|
||||
message.reply(`Incorrect syntax!\n**Expected:** *${botName} ${commandObj.syntax}*\n**Need help?** *${botName} ${config.commands.help.command}*`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
Bot.onNonCommandMsg(message, guildData);
|
||||
}
|
||||
};
|
||||
|
||||
function fromJSON(json) {
|
||||
const guildsData = Object.keys(json);
|
||||
guildsData.forEach(guildID => { json[guildID] = new GuildData(json[guildID]); });
|
||||
return json;
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function createHelpEmbed(name, config, userIsAdmin) {
|
||||
const commandsArr = Object.keys(config.commands).map(x => config.commands[x]).filter(x => userIsAdmin || !x.admin);
|
||||
function doGuildIteration() {
|
||||
const guild = guildsIterator.next().value;
|
||||
|
||||
const embed = new Discord.RichEmbed().setTitle("__Help__");
|
||||
if (guild)
|
||||
client.guildDataModel.findOne({ guildID: guild.id })
|
||||
.then(guildData => guildData && checkGuildFeeds(guild, guildData));
|
||||
}
|
||||
|
||||
commandsArr.forEach(command => {
|
||||
embed.addField(command.command, `${command.description}\n**Usage:** *${name} ${command.syntax}*${userIsAdmin && command.admin ? "\n***Admin only***" : ""}`);
|
||||
});
|
||||
function checkGuildFeeds(guild, guildData) {
|
||||
guildData.checkFeeds(guild)
|
||||
.then(values => values.some(x => x) && guildData.save());
|
||||
}
|
||||
|
||||
embed.addField("__Need more help?__", `[Visit my website](${config.generic.website}) or [Join my Discord](${config.generic.discordInvite})`, true);
|
||||
function cacheUrlsInMessage(message, guildData) {
|
||||
const anyNewLinksPosted = [];
|
||||
|
||||
return { embed };
|
||||
guildData.feeds
|
||||
.filter(feedData => message.channel.id === feedData.channelID)
|
||||
.forEach(feedData => anyNewLinksPosted.push(feedData.cache(...GetUrls(message.content))));
|
||||
|
||||
if (anyNewLinksPosted.some(x => x))
|
||||
guildData.save();
|
||||
}
|
30
app/legacy-upgrader.js
Normal file
30
app/legacy-upgrader.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
// @ts-nocheck
|
||||
const NewGuildData = require("./models/guild-data.js");
|
||||
const NewFeedData = require("./models/feed-data.js");
|
||||
const FileSystem = require("fs");
|
||||
|
||||
module.exports = function () {
|
||||
if (!FileSystem.existsSync("./guilds.json"))
|
||||
return;
|
||||
|
||||
const legacyJson = require("../guilds.json");
|
||||
|
||||
for (let guildID of Object.keys(legacyJson)) {
|
||||
const guildData = NewGuildData.create({ guildID });
|
||||
|
||||
for (let feed of legacyJson[guildID].feeds) {
|
||||
guildData.feeds.push(NewFeedData.create({
|
||||
feedID: feed.id,
|
||||
url: feed.url,
|
||||
roleID: feed.roleID,
|
||||
channelID: feed.channelID,
|
||||
cachedLinks: feed.cachedLinks,
|
||||
maxCacheSize: feed.maxCacheSize
|
||||
}));
|
||||
}
|
||||
|
||||
guildData.save();
|
||||
}
|
||||
|
||||
FileSystem.rename("./guilds.json", "./guilds.json.backup");
|
||||
};
|
|
@ -1,89 +1,138 @@
|
|||
//my imports
|
||||
const DiscordUtil = require("discordjs-util");
|
||||
// @ts-ignore
|
||||
const Config = require("../config.json");
|
||||
|
||||
//external lib imports
|
||||
const Dns = require("dns"); //for host resolution checking
|
||||
const Url = require("url"); //for url parsing
|
||||
const FeedRead = require("feed-read"); //for extracing new links from RSS feeds
|
||||
const GetUrls = require("get-urls"); //for extracting urls from messages
|
||||
const ShortID = require("shortid"); //to provide ids for each feed, allowing guilds to remove them
|
||||
const { promisify } = require("util");
|
||||
const Core = require("../../core");
|
||||
const DiscordUtil = require("../../core").util;
|
||||
const GetUrls = require("get-urls");
|
||||
const Url = require("url");
|
||||
|
||||
module.exports = class FeedData {
|
||||
constructor({ id, url, channelName, roleName, cachedLinks, maxCacheSize }) {
|
||||
this.id = id || ShortID.generate();
|
||||
this.url = url;
|
||||
this.channelName = channelName;
|
||||
this.roleName = roleName;
|
||||
this.cachedLinks = cachedLinks || [];
|
||||
this.maxCacheSize = maxCacheSize || 10;
|
||||
// @ts-ignore
|
||||
const readFeed = url => promisify(require("rss-parser").parseURL)(url);
|
||||
const resolveDns = promisify(require("dns").resolve);
|
||||
|
||||
this.cachedLinks.push = (...elements) => {
|
||||
const unique = elements
|
||||
.map(el => normaliseUrl(el)) //normalise all the urls
|
||||
.filter(el => !this.cachedLinks.includes(el)); //filter out any already cached
|
||||
Array.prototype.push.apply(this.cachedLinks, unique);
|
||||
module.exports = class FeedData extends Core.BaseEmbeddedData {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (this.cachedLinks.length > this.maxCacheSize)
|
||||
this.cachedLinks.splice(0, this.cachedLinks.length - this.maxCacheSize); //remove the # of elements above the max from the beginning
|
||||
};
|
||||
}
|
||||
this.feedID = "";
|
||||
this.url = "";
|
||||
this.channelID = "";
|
||||
this.roleID = "";
|
||||
this.cachedLinks = [];
|
||||
this.maxCacheSize = 100;
|
||||
|
||||
/**
|
||||
* Returns a promise providing all the links posted in the last 100 messages
|
||||
* @param {Discord.Guild} guild The guild this feed belongs to
|
||||
* @returns {Promise<string[]>} Links posted in last 100 messages
|
||||
*/
|
||||
updatePastPostedLinks(guild) {
|
||||
const channel = guild.channels.find(ch => ch.type === "text" && ch.name === this.channelName);
|
||||
// @ts-ignore
|
||||
this.schema({
|
||||
feedID: String,
|
||||
url: String,
|
||||
channelID: String,
|
||||
roleID: String,
|
||||
cachedLinks: [String],
|
||||
maxCacheSize: Number
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
channel.fetchMessages({ limit: 100 })
|
||||
.then(messages => {
|
||||
new Map([...messages].reverse()).forEach(m => this.cachedLinks.push(...GetUrls(m.content))); //push all the links in each message into our links array
|
||||
resolve(this);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
cache(...elements) {
|
||||
const newArticles = elements
|
||||
.map(el => normaliseUrlForCache(el))
|
||||
.filter(el => !this._isCached(el));
|
||||
|
||||
check(guild) {
|
||||
Dns.resolve(Url.parse(this.url).host || "", err => { //check we can resolve the host, so we can throw an appropriate error if it fails
|
||||
if (err)
|
||||
DiscordUtil.dateError("Connection Error: Can't resolve host", err); //log our error if we can't resolve the host
|
||||
else
|
||||
FeedRead(this.url, (err, articles) => { //check the feed
|
||||
if (err)
|
||||
DiscordUtil.dateError(err);
|
||||
else {
|
||||
let latest = articles[0].link; //extract the latest link
|
||||
latest = normaliseUrl(latest); //standardise it a bit
|
||||
Array.prototype.push.apply(this.cachedLinks, newArticles);
|
||||
|
||||
//if we don't have it cached already, cache it and callback
|
||||
if (!this.cachedLinks.includes(latest)) {
|
||||
this.cachedLinks.push(latest);
|
||||
this.cachedLinks.splice(0, this.cachedLinks.length - this.maxCacheSize); //seeing as new links come in at the end of the array, we need to remove the old links from the beginning
|
||||
|
||||
const channel = guild.channels.find(ch => ch.type === "text" && ch.name.toLowerCase() === this.channelName.toLowerCase());
|
||||
const role = this.roleName ? guild.roles.find(role => role.name.toLowerCase() === this.roleName.toLowerCase()) : null;
|
||||
channel.send((role ? role + " " : "") + latest);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return elements.length > 0;
|
||||
}
|
||||
|
||||
toString() {
|
||||
const blacklist = ["cachedLinks", "maxCacheSize"];
|
||||
return `\`\`\`JavaScript\n ${JSON.stringify(this, (k, v) => !blacklist.includes(k) ? v : undefined, "\t")} \`\`\``;
|
||||
}
|
||||
updatePastPostedLinks(guild) {
|
||||
const channel = guild.channels.get(this.channelID);
|
||||
|
||||
if (!channel)
|
||||
return Promise.reject("Channel not found!");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
channel.fetchMessages({ limit: 100 })
|
||||
.then(messages => {
|
||||
/* we want to push the links in oldest first, but discord.js returns messages newest first, so we need to reverse them
|
||||
* discord.js returns a map, and maps don't have .reverse methods, hence needing to spread the elements into an array first */
|
||||
[...messages.values()].reverse().forEach(m => this.cache(...GetUrls(m.content)));
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
fetchLatest(guild) {
|
||||
const dnsPromise = resolveDns(Url.parse(this.url).host).then(() => this._doFetchRSS(guild));
|
||||
|
||||
dnsPromise.catch(err => DiscordUtil.dateDebugError("Connection error: Can't resolve host", err.message || err));
|
||||
|
||||
return dnsPromise;
|
||||
}
|
||||
|
||||
toString() {
|
||||
const blacklist = ["cachedLinks", "maxCacheSize"];
|
||||
return `\`\`\`JavaScript\n ${JSON.stringify(this, (k, v) => !blacklist.find(x => x === k) ? v : undefined, "\t")} \`\`\``;
|
||||
}
|
||||
|
||||
_isCached(url) {
|
||||
return this.cachedLinks.indexOf(normaliseUrlForCache(url)) > -1;
|
||||
}
|
||||
|
||||
_doFetchRSS(guild) {
|
||||
const feedPromise = readFeed(this.url).then(parsed => this._processLatestArticle(guild, parsed.feed.entries));
|
||||
|
||||
feedPromise.catch(err => DiscordUtil.dateDebugError([`Error reading feed ${this.url}`, err]));
|
||||
|
||||
return feedPromise;
|
||||
}
|
||||
|
||||
_processLatestArticle(guild, entries) {
|
||||
if (entries.length === 0 || !entries[0].link)
|
||||
return false;
|
||||
|
||||
if (this._isCached(entries[0].link))
|
||||
return false;
|
||||
|
||||
this.cache(entries[0].link);
|
||||
|
||||
const channel = guild.channels.get(this.channelID),
|
||||
role = guild.roles.get(this.roleID);
|
||||
|
||||
channel.send((role || "") + formatPost(entries[0]))
|
||||
.catch(err => DiscordUtil.dateDebugError(`Error posting in ${channel.id}: ${err.message || err}`));
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
function normaliseUrl(url) {
|
||||
url = url.replace("https://", "http://"); //hacky way to treat http and https the same
|
||||
function formatPost(article) {
|
||||
let message = "";
|
||||
|
||||
if (Url.parse(url).host.startsWith("http://youtu"))
|
||||
url = url.split("?")[0]; //quick way to chop off stuff like ?feature=youtube
|
||||
if (article.title) message += `\n**${article.title}**`;
|
||||
if (article.content) message += article.content.length > Config.charLimit ? "" : `\n${article.content}`;
|
||||
if (article.link) message += `\n\n${normaliseUrlForDiscord(article.link)}`;
|
||||
|
||||
url = url.replace(/(www.)?youtube.com\/watch\?v=/, "youtu.be/"); //convert youtube full url to short
|
||||
return message;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
function normaliseUrlForDiscord(url) {
|
||||
const parsedUrl = Url.parse(url);
|
||||
if (parsedUrl.host && parsedUrl.host.includes("youtube.com"))
|
||||
url = normaliseYouTubeUrl(url, parsedUrl);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
function normaliseYouTubeUrl(origUrl, parsedUrl) {
|
||||
const videoIDParam = parsedUrl.query ? parsedUrl.query.split("&").find(x => x.startsWith("v=")) : null;
|
||||
if (!videoIDParam)
|
||||
return origUrl;
|
||||
const videoID = videoIDParam.substring(videoIDParam.indexOf("=") + 1, videoIDParam.length);
|
||||
return `http://youtu.be/${videoID}`
|
||||
}
|
||||
|
||||
function normaliseUrlForCache(url) {
|
||||
return normaliseUrlForDiscord(url).replace(/^((https?:\/\/)?(www.)?)/, "");
|
||||
}
|
||||
|
|
|
@ -1,23 +1,34 @@
|
|||
const Core = require("../../core");
|
||||
const FeedData = require("./feed-data.js");
|
||||
const Util = require("discordjs-util");
|
||||
|
||||
module.exports = class GuildData {
|
||||
constructor({ id, feeds }) {
|
||||
this.id = id;
|
||||
this.feeds = (feeds || []).map(feed => new FeedData(feed));
|
||||
}
|
||||
module.exports = class GuildData extends Core.BaseGuildData {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
cachePastPostedLinks(guild) {
|
||||
const promises = [];
|
||||
this.feeds = [];
|
||||
|
||||
this.feeds.forEach(feed => {
|
||||
promises.push(feed.updatePastPostedLinks(guild).catch(Util.dateError));
|
||||
});
|
||||
this.schema({
|
||||
feeds: [FeedData]
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
cachePastPostedLinks(guild) {
|
||||
return Promise.all(
|
||||
this.feeds
|
||||
.filter(feed => feedIsActive(feed, guild))
|
||||
.map(feed => feed.updatePastPostedLinks(guild).catch(err => null))
|
||||
);
|
||||
}
|
||||
|
||||
checkFeeds(guilds) {
|
||||
this.feeds.forEach(feed => feed.check(guilds.get(this.id)));
|
||||
}
|
||||
};
|
||||
checkFeeds(guild) {
|
||||
return Promise.all(
|
||||
this.feeds
|
||||
.filter(feed => feedIsActive(feed, guild))
|
||||
.map(feed => feed.fetchLatest(guild).catch(err => null))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function feedIsActive(feed, guild) {
|
||||
return guild.channels.get(feed.channelID);
|
||||
}
|
20
bootstrap.js
vendored
20
bootstrap.js
vendored
|
@ -1,20 +0,0 @@
|
|||
const Discord = require("discord.js");
|
||||
const DiscordUtil = require("discordjs-util");
|
||||
|
||||
const client = new Discord.Client();
|
||||
|
||||
process.on("uncaughtException", (err) => {
|
||||
DiscordUtil.dateError("Uncaught exception!", err);
|
||||
});
|
||||
|
||||
client.login(require("./token.json").token);
|
||||
|
||||
client.on("ready", () => {
|
||||
DiscordUtil.dateLog("Registered bot " + client.user.username);
|
||||
require("./app/index.js")(client);
|
||||
client.user.setPresence({ game: { name: "benji7425.github.io", type: 0 } });
|
||||
});
|
||||
|
||||
client.on("disconnect", eventData => {
|
||||
DiscordUtil.dateError("Bot was disconnected!", eventData.code, eventData.reason);
|
||||
});
|
79
core/.gitignore
vendored
Normal file
79
core/.gitignore
vendored
Normal file
|
@ -0,0 +1,79 @@
|
|||
### Discord bots ####
|
||||
guilds.json
|
||||
token.json
|
||||
log
|
||||
|
||||
|
||||
# Created by https://www.gitignore.io/api/node,visualstudiocode
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_cache
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history
|
||||
|
||||
# End of https://www.gitignore.io/api/node,visualstudiocode
|
12
core/.gitrepo
Normal file
12
core/.gitrepo
Normal file
|
@ -0,0 +1,12 @@
|
|||
; DO NOT EDIT (unless you know what you are doing)
|
||||
;
|
||||
; This subdirectory is a git "subrepo", and this file is maintained by the
|
||||
; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
|
||||
;
|
||||
[subrepo]
|
||||
remote = git@github.com:benji7425/discord-bot-core.git
|
||||
branch = master
|
||||
commit = 7a3eed3eff576ed51cfdfbbad18df07d4fcf52a7
|
||||
parent = 09cf5233db442fa0af452e84e90289619532418a
|
||||
method = merge
|
||||
cmdver = 0.3.1
|
2
core/.npmrc
Normal file
2
core/.npmrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
save=true
|
||||
save-exact=true
|
21
core/LICENSE
Normal file
21
core/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Benjamin Higgins
|
||||
|
||||
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.
|
27
core/README.md
Normal file
27
core/README.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Discord bot core
|
||||
|
||||
Core code for connection and command handling for my Discord bots.
|
||||
This is not actually a Discord bot. To learn about some of my available Discord bots please visit [my website](https://benji7425.github.io/)
|
||||
This code is included in the repo for each bot using [git subrepo](https://github.com/ingydotnet/git-subrepo), which I prefer over submodules and subtrees.
|
||||
|
||||
This code does not function on it's own, it simply provides common functionality my Discord bots can make use of.
|
||||
To test/modify this code, it needs to be run in the context of an actual bot.
|
||||
A good candidate is my [template project](https://github.com/benji7425/discord-bot-template), which should run and connect just fine, just won't do much.
|
||||
|
||||
## Built with
|
||||
- [discord.js](https://discord.js.org/#/)
|
||||
- [NeDB](https://github.com/louischatriot/nedb)
|
||||
- [camo](https://github.com/scottwrobinson/camo)
|
||||
|
||||
## Contributing
|
||||
|
||||
The easiest way to get setup would be a fork of this repo, and the fork of a bot to use as a development context.
|
||||
I suggest using my [template project](https://github.com/benji7425/discord-bot-template) as a development context, along with [git subrepo](https://github.com/ingydotnet/git-subrepo) to push back to your core fork.
|
||||
If you are uncomfortable using git subrepo, you could just as easily copy/paste your child `core` directory back into your fork.
|
||||
|
||||
My [template project](https://github.com/benji7425/discord-bot-template) has more in-depth details about how to setup and test.
|
||||
|
||||
If you have a completed change, please submit a pull request from your `core` fork back to this repo.
|
||||
|
||||
Whilst this is very easy to use once you know how, I appreciate this might be a little tricky if you aren't familiar with developing in this way.
|
||||
Feel free to contact me if you would like assistance. Find contact details [here](https://benji7425.github.io/contact).
|
8
core/base-embedded-data.js
Normal file
8
core/base-embedded-data.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
const Camo = require("camo");
|
||||
|
||||
// @ts-ignore
|
||||
module.exports = class BaseEmbeddedData extends Camo.EmbeddedDocument {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
};
|
9
core/base-guild-data.js
Normal file
9
core/base-guild-data.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const Camo = require("camo");
|
||||
|
||||
module.exports = class BaseGuildData extends Camo.Document {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.guildID = String;
|
||||
}
|
||||
};
|
83
core/client.js
Normal file
83
core/client.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
const CoreUtil = require("./util.js");
|
||||
const Camo = require("camo");
|
||||
const CronJob = require("cron").CronJob;
|
||||
const Discord = require("discord.js");
|
||||
const HandleGuildMessage = require("./handle-guild-message.js");
|
||||
// @ts-ignore
|
||||
const InternalConfig = require("./internal-config.json");
|
||||
const RequireAll = require("require-all");
|
||||
|
||||
let database;
|
||||
|
||||
module.exports = class Client extends Discord.Client {
|
||||
/**
|
||||
* Construct a new Discord.Client with some added functionality
|
||||
* @param {string} token bot token
|
||||
* @param {string} commandsDir location of dir containing commands .js files
|
||||
* @param {*} guildDataModel GuildData model to be used for app; must extend BaseGuildData
|
||||
*/
|
||||
constructor(token, commandsDir, guildDataModel) {
|
||||
super({
|
||||
messageCacheMaxSize: 16,
|
||||
disabledEvents: InternalConfig.disabledEvents
|
||||
});
|
||||
|
||||
this._token = token;
|
||||
this.commandsDir = commandsDir;
|
||||
this.guildDataModel = guildDataModel;
|
||||
|
||||
this.commands = RequireAll(this.commandsDir);
|
||||
|
||||
this.on("ready", this._onReady);
|
||||
this.on("message", this._onMessage);
|
||||
this.on("debug", this._onDebug);
|
||||
this.on("guildCreate", this._onGuildCreate);
|
||||
process.on("uncaughtException", err => this._onUnhandledException(this, err));
|
||||
}
|
||||
|
||||
_onReady() {
|
||||
this.user.setGame(InternalConfig.website.replace(/^https?:\/\//, ""));
|
||||
CoreUtil.dateLog(`Registered bot ${this.user.username}`);
|
||||
}
|
||||
|
||||
_onMessage(message) {
|
||||
if (message.channel.type === "text" && message.member)
|
||||
HandleGuildMessage(this, message, this.commands);
|
||||
}
|
||||
|
||||
_onDebug(info) {
|
||||
info = info.replace(/Authenticated using token [^ ]+/, "Authenticated using token [redacted]");
|
||||
if (!InternalConfig.debugIgnores.some(x => info.startsWith(x)))
|
||||
CoreUtil.dateDebug(info);
|
||||
}
|
||||
|
||||
_onGuildCreate(guild) {
|
||||
CoreUtil.dateLog(`Added to guild ${guild.name}`);
|
||||
}
|
||||
|
||||
_onUnhandledException(client, err) {
|
||||
CoreUtil.dateError("Unhandled exception!\n", err);
|
||||
CoreUtil.dateLog("Destroying existing client...");
|
||||
client.destroy().then(() => {
|
||||
CoreUtil.dateLog("Client destroyed, recreating...");
|
||||
setTimeout(() => client.login(client._token), InternalConfig.reconnectTimeout);
|
||||
});
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
Camo.connect(InternalConfig.dbConnectionString).then(db => {
|
||||
database = db;
|
||||
|
||||
const dbProtocol = InternalConfig.dbConnectionString.match(/^(.+):\/\//)[1];
|
||||
CoreUtil.dateLog(`Database protocol: ${dbProtocol}`);
|
||||
|
||||
if (dbProtocol === "nedb") {
|
||||
CoreUtil.dateLog(`Seting up NeDB collection compaction cron job; schedule: ${InternalConfig.neDBCompactionSchedule}`);
|
||||
new CronJob(InternalConfig.neDBCompactionSchedule, () => database.compactCollectionFiles(), null, true);
|
||||
}
|
||||
|
||||
this.emit("beforeLogin");
|
||||
this.login(this._token);
|
||||
});
|
||||
}
|
||||
};
|
15
core/command.js
Normal file
15
core/command.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
module.exports = class Command {
|
||||
constructor({ name, description, syntax, admin, invoke }) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.syntax = syntax;
|
||||
this.admin = admin;
|
||||
this.invoke = invoke;
|
||||
|
||||
const params = this.syntax.split(/ +/);
|
||||
const optionalParams = params.filter(x => x.match(/^\[.+\]$/));
|
||||
|
||||
this.maxParamCount = params.length - 1;
|
||||
this.expectedParamCount = this.maxParamCount - optionalParams.length;
|
||||
}
|
||||
};
|
30
core/commands/help.js
Normal file
30
core/commands/help.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
const Command = require("../command.js");
|
||||
const Discord = require("discord.js");
|
||||
const InternalConfig = require("../internal-config.json");
|
||||
const ParentPackageJson = require("../../package.json");
|
||||
|
||||
module.exports = new Command({
|
||||
name: "help",
|
||||
description: "Display available commands with descriptions",
|
||||
syntax: "help",
|
||||
admin: false,
|
||||
invoke
|
||||
});
|
||||
|
||||
function invoke({ commands, isMemberAdmin, client }) {
|
||||
return Promise.resolve(createHelpEmbed(ParentPackageJson.name, commands, isMemberAdmin, client.user.username));
|
||||
}
|
||||
|
||||
function createHelpEmbed(name, commands, userIsAdmin, username) {
|
||||
const commandsArr = Object.keys(commands).map(x => commands[x]).filter(x => userIsAdmin || !x.admin);
|
||||
|
||||
const embed = new Discord.RichEmbed().setTitle(`__${(ParentPackageJson.name + "").replace("discord-bot-", "")} help__`);
|
||||
|
||||
commandsArr.forEach(command => {
|
||||
embed.addField(command.name, `${command.description}\n**Usage:** *@${username} ${command.syntax}*${userIsAdmin && command.admin ? "\n***Admin only***" : ""}`);
|
||||
});
|
||||
|
||||
embed.addField("__Need more help?__", `[Visit my website](${InternalConfig.website}) or [Join my Discord](${InternalConfig.discordInvite})`, true);
|
||||
|
||||
return { embed };
|
||||
}
|
31
core/commands/reset.js
Normal file
31
core/commands/reset.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
const Command = require("../command.js");
|
||||
const Util = require("../util.js");
|
||||
|
||||
module.exports = new Command({
|
||||
name: "reset",
|
||||
description: "Reset all data for this Discord server. WARNING: YOU WILL LOSE ALL YOUR SETTINGS!",
|
||||
syntax: "reset",
|
||||
admin: true,
|
||||
invoke
|
||||
});
|
||||
|
||||
function invoke({ guildData, client, message }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
/* this is a very hacky way of doing this, but when using .resolve()
|
||||
the guildData object gets saved back to the database straight away,
|
||||
meaning it'd be deleted and instnantly re-created. Using .reject
|
||||
means that .save doesn't get called by the parent. Very hacky but works. */
|
||||
|
||||
Util.ask(client, message.channel, message.member, "Are you sure you want to delete all the data for this server? (yes/no)")
|
||||
.then(response => {
|
||||
if (response.toLowerCase() === "yes")
|
||||
guildData
|
||||
.delete()
|
||||
.then(() => reject("Data for this server successfully deleted"));
|
||||
else
|
||||
reject("Guild data was not deleted");
|
||||
});
|
||||
// .then(() => resolve("Data for this server successfully deleted"))
|
||||
// .catch(() => reject("Error deleting data for this server"));
|
||||
});
|
||||
}
|
31
core/commands/stats.js
Normal file
31
core/commands/stats.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
const Command = require("../command.js");
|
||||
|
||||
module.exports = new Command({
|
||||
name: "stats",
|
||||
description: "Show some stats about the bot",
|
||||
syntax: "stats",
|
||||
admin: false,
|
||||
invoke
|
||||
});
|
||||
|
||||
function invoke({ message, params, guildData, client }) {
|
||||
return Promise.resolve(`
|
||||
**Server count:** ${client.guilds.size}
|
||||
**Cached users:** ${client.users.size}
|
||||
**Uptime:** ${toHHMMSS(client.uptime)}
|
||||
`);
|
||||
}
|
||||
|
||||
function toHHMMSS(ms) {
|
||||
const secsTruncated = Math.trunc(ms / 1000); // don't forget the second param
|
||||
const hrs = Math.floor(secsTruncated / 3600);
|
||||
const mins = Math.floor((secsTruncated - (hrs * 3600)) / 60);
|
||||
const secs = secsTruncated - (hrs * 3600) - (mins * 60);
|
||||
|
||||
let hoursStr = hrs.toString(), minsStr = mins.toString(), secsStr = secs.toString();
|
||||
|
||||
if (hrs < 10) { hoursStr = "0" + hrs; }
|
||||
if (mins < 10) { minsStr = "0" + mins; }
|
||||
if (secs < 10) { secsStr = "0" + secs; }
|
||||
return hoursStr + ":" + minsStr + ":" + secsStr;
|
||||
}
|
15
core/commands/version.js
Normal file
15
core/commands/version.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
const Command = require("../command.js");
|
||||
// @ts-ignore
|
||||
const ParentPackageJson = require("../../package.json");
|
||||
|
||||
module.exports = new Command({
|
||||
name: "version",
|
||||
description: "Return version number",
|
||||
syntax: "version",
|
||||
admin: false,
|
||||
invoke
|
||||
});
|
||||
|
||||
function invoke() {
|
||||
return Promise.resolve(`${(ParentPackageJson.name + "").replace("discord-bot-", "")} v${ParentPackageJson.version}`);
|
||||
}
|
54
core/handle-guild-message.js
Normal file
54
core/handle-guild-message.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
const RequireAll = require("require-all");
|
||||
|
||||
const internalCommands = RequireAll(__dirname + "/commands");
|
||||
|
||||
function handleGuildMessage(client, message, commands) {
|
||||
if (isCommand(message))
|
||||
client.guildDataModel.findOne({ guildID: message.guild.id })
|
||||
.then(guildData =>
|
||||
handleGuildCommand(
|
||||
client,
|
||||
message,
|
||||
Object.assign({}, internalCommands, commands),
|
||||
guildData || client.guildDataModel.create({ guildID: message.guild.id })
|
||||
));
|
||||
}
|
||||
|
||||
function handleGuildCommand(client, message, commands, guildData) {
|
||||
const { botName, isMemberAdmin, params, command } = parseDetails(message, commands);
|
||||
|
||||
if (!command)
|
||||
return;
|
||||
|
||||
if (params.length < command.expectedParamCount)
|
||||
message.reply(`Incorrect syntax!\n**Expected:** *${botName} ${command.syntax}*\n**Need help?** *${botName} help*`);
|
||||
|
||||
else if (isMemberAdmin || !command.admin)
|
||||
command.invoke({ message, params, guildData, client, commands, isMemberAdmin })
|
||||
.then(response => {
|
||||
guildData.save()
|
||||
.then(() => response && message.reply(response))
|
||||
.catch(() => message.reply("Error saving"));
|
||||
})
|
||||
.catch(err => err && message.reply(err));
|
||||
}
|
||||
|
||||
function parseDetails(message, commands) {
|
||||
const split = message.content.split(/ +/);
|
||||
const commandName = Object.keys(commands).find(x =>
|
||||
/**/ commands[x].name.toLowerCase() === (split[1] || "").toLowerCase());
|
||||
|
||||
return {
|
||||
botName: "@" + (message.guild.me.nickname || message.guild.me.user.username),
|
||||
isMemberAdmin: message.member.permissions.has("ADMINISTRATOR"),
|
||||
params: split.slice(2, split.length),
|
||||
command: commands[commandName]
|
||||
};
|
||||
}
|
||||
|
||||
function isCommand(message) {
|
||||
//criteria for a command is bot being mentioned
|
||||
return new RegExp(`^<@!?${/[0-9]{18}/.exec(message.guild.me.toString())[0]}>`).exec(message.content);
|
||||
}
|
||||
|
||||
module.exports = handleGuildMessage;
|
14
core/index.js
Normal file
14
core/index.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
// @ts-ignore
|
||||
const InternalConfig = require("./internal-config.json");
|
||||
|
||||
module.exports = {
|
||||
Client: require("./client.js"),
|
||||
BaseGuildData: require("./base-guild-data.js"),
|
||||
BaseEmbeddedData: require("./base-embedded-data.js"),
|
||||
Command: require("./command.js"),
|
||||
util: require("./util.js"),
|
||||
details: {
|
||||
website: InternalConfig.website,
|
||||
discordInvite: InternalConfig.discordInvite
|
||||
}
|
||||
};
|
20
core/internal-config.json
Normal file
20
core/internal-config.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"dbConnectionString": "nedb://guilds-data",
|
||||
"neDBCompactionSchedule": "0 0 * * * *",
|
||||
"website": "https://benji7425.github.io",
|
||||
"discordInvite": "https://discord.gg/SSkbwSJ",
|
||||
"debugIgnores": [
|
||||
"[ws] [connection] Sending a heartbeat",
|
||||
"[ws] [connection] Heartbeat acknowledged"
|
||||
],
|
||||
"disabledEvents": [
|
||||
"CHANNEL_PINS_UPDATE",
|
||||
"GUILD_BAN_ADD",
|
||||
"GUILD_BAN_REMOVE",
|
||||
"PRESENCE_UPDATE",
|
||||
"TYPING_START",
|
||||
"USER_NOTE_UPDATE",
|
||||
"USER_SETTINGS_UPDATE"
|
||||
],
|
||||
"askTimeout": 60000
|
||||
}
|
475
core/package-lock.json
generated
Normal file
475
core/package-lock.json
generated
Normal file
|
@ -0,0 +1,475 @@
|
|||
{
|
||||
"name": "discord-bot-core",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "8.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.1.tgz",
|
||||
"integrity": "sha512-4JFGIC1RSoFngVsT5EZcL793/uRi/OJ3ilsp9DQUr4LZOaMhNM1pPrt9TqlXOnXj3h73hl6NF31v87eQAPXYTg=="
|
||||
},
|
||||
"acorn": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
|
||||
"integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ="
|
||||
},
|
||||
"amdefine": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
|
||||
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
|
||||
},
|
||||
"ast-types": {
|
||||
"version": "0.8.15",
|
||||
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.15.tgz",
|
||||
"integrity": "sha1-ju8IJ/BN/w7IhXupJavj/qYZTlI="
|
||||
},
|
||||
"async": {
|
||||
"version": "0.2.10",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
|
||||
"integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E="
|
||||
},
|
||||
"async-limiter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
||||
"integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg="
|
||||
},
|
||||
"base62": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz",
|
||||
"integrity": "sha1-e0F0wvlESXU7EcJlHAg9qEGnsIQ="
|
||||
},
|
||||
"binary-search-tree": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/binary-search-tree/-/binary-search-tree-0.2.5.tgz",
|
||||
"integrity": "sha1-fbs7IQ/coIJFDa0jNMMErzm9x4Q=",
|
||||
"requires": {
|
||||
"underscore": "1.4.4"
|
||||
}
|
||||
},
|
||||
"bson": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz",
|
||||
"integrity": "sha1-k8ENOeqltYQVy8QFLz5T5WKwtyw=",
|
||||
"optional": true
|
||||
},
|
||||
"buffer-shims": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
|
||||
"integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=",
|
||||
"optional": true
|
||||
},
|
||||
"camo": {
|
||||
"version": "git+https://github.com/benji7425/camo.git#51d63d2441827b430884400f511ae911f1961727",
|
||||
"requires": {
|
||||
"depd": "1.1.0",
|
||||
"lodash": "3.9.3",
|
||||
"mongodb": "2.2.34",
|
||||
"nedb": "1.8.0"
|
||||
}
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"cron": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cron/-/cron-1.3.0.tgz",
|
||||
"integrity": "sha512-K/SF7JlgMmNjcThWxkKvsHhey2EDB4CeOEWJ9aXWj3fbQJppsvTPIeyLdHfNq5IbbsMUUjRW1nr5dSO95f2E4w==",
|
||||
"requires": {
|
||||
"moment-timezone": "0.5.14"
|
||||
}
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz",
|
||||
"integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM="
|
||||
},
|
||||
"discord.js": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-11.2.0.tgz",
|
||||
"integrity": "sha1-qt3iuGtpuQUWa4O8Sc/ENSOOGus=",
|
||||
"requires": {
|
||||
"long": "3.2.0",
|
||||
"prism-media": "0.0.1",
|
||||
"snekfetch": "3.6.1",
|
||||
"tweetnacl": "1.0.0",
|
||||
"ws": "3.3.2"
|
||||
}
|
||||
},
|
||||
"es3ify": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/es3ify/-/es3ify-0.1.4.tgz",
|
||||
"integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E=",
|
||||
"requires": {
|
||||
"esprima-fb": "3001.1.0-dev-harmony-fb",
|
||||
"jstransform": "3.0.0",
|
||||
"through": "2.3.8"
|
||||
}
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
|
||||
"integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=",
|
||||
"optional": true
|
||||
},
|
||||
"esmangle-evaluator": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esmangle-evaluator/-/esmangle-evaluator-1.0.1.tgz",
|
||||
"integrity": "sha1-Yg2GbvSGGzMR91dm1SqFcrs8YzY="
|
||||
},
|
||||
"esprima-fb": {
|
||||
"version": "3001.1.0-dev-harmony-fb",
|
||||
"resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz",
|
||||
"integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE="
|
||||
},
|
||||
"falafel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz",
|
||||
"integrity": "sha1-wY0k71CRF0pJfzGM0ksCaiXN2rQ=",
|
||||
"requires": {
|
||||
"acorn": "1.2.2",
|
||||
"foreach": "2.0.5",
|
||||
"isarray": "0.0.1",
|
||||
"object-keys": "1.0.11"
|
||||
}
|
||||
},
|
||||
"foreach": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
|
||||
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
|
||||
},
|
||||
"immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"inline-process-browser": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/inline-process-browser/-/inline-process-browser-1.0.0.tgz",
|
||||
"integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI=",
|
||||
"requires": {
|
||||
"falafel": "1.2.0",
|
||||
"through2": "0.6.5"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||
},
|
||||
"jstransform": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz",
|
||||
"integrity": "sha1-olkats7o2XvzvoMNv6IxO4fNZAs=",
|
||||
"requires": {
|
||||
"base62": "0.1.1",
|
||||
"esprima-fb": "3001.1.0-dev-harmony-fb",
|
||||
"source-map": "0.1.31"
|
||||
}
|
||||
},
|
||||
"lie": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.0.2.tgz",
|
||||
"integrity": "sha1-/9oh17uibzd8rYZdNkmy/Izjn+o=",
|
||||
"requires": {
|
||||
"es3ify": "0.1.4",
|
||||
"immediate": "3.0.6",
|
||||
"inline-process-browser": "1.0.0",
|
||||
"unreachable-branch-transform": "0.3.0"
|
||||
}
|
||||
},
|
||||
"localforage": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.5.5.tgz",
|
||||
"integrity": "sha1-VfwcOoikf2f1+sbxIxsl/xNVZCM=",
|
||||
"requires": {
|
||||
"lie": "3.0.2"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "3.9.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.9.3.tgz",
|
||||
"integrity": "sha1-AVnoaDL+/8bWHYUrEqlTuZSWvTI="
|
||||
},
|
||||
"long": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
|
||||
"integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s="
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.19.3",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.19.3.tgz",
|
||||
"integrity": "sha1-vbmdJw1tf9p4zA+6zoVeJ/59pp8="
|
||||
},
|
||||
"moment-timezone": {
|
||||
"version": "0.5.14",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz",
|
||||
"integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=",
|
||||
"requires": {
|
||||
"moment": "2.19.3"
|
||||
}
|
||||
},
|
||||
"mongodb": {
|
||||
"version": "2.2.34",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.34.tgz",
|
||||
"integrity": "sha1-o09Zu+thdUrsQy3nLD/iFSakTBo=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"es6-promise": "3.2.1",
|
||||
"mongodb-core": "2.1.18",
|
||||
"readable-stream": "2.2.7"
|
||||
}
|
||||
},
|
||||
"mongodb-core": {
|
||||
"version": "2.1.18",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.18.tgz",
|
||||
"integrity": "sha1-TEYTm986HwMt7ZHbSfOO7AFlkFA=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bson": "1.0.4",
|
||||
"require_optional": "1.0.1"
|
||||
}
|
||||
},
|
||||
"nedb": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/nedb/-/nedb-1.8.0.tgz",
|
||||
"integrity": "sha1-DjUCzYLABNU1WkPJ5VV3vXvZHYg=",
|
||||
"requires": {
|
||||
"async": "0.2.10",
|
||||
"binary-search-tree": "0.2.5",
|
||||
"localforage": "1.5.5",
|
||||
"mkdirp": "0.5.1",
|
||||
"underscore": "1.4.4"
|
||||
}
|
||||
},
|
||||
"object-keys": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
|
||||
"integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0="
|
||||
},
|
||||
"parent-package-json": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-package-json/-/parent-package-json-2.0.1.tgz",
|
||||
"integrity": "sha1-SbVUqeLcyu1F1+GiXqjOyde7axI="
|
||||
},
|
||||
"prism-media": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-0.0.1.tgz",
|
||||
"integrity": "sha1-o0JcnKvVDRxsAuVDlBoRiVZnvRA="
|
||||
},
|
||||
"private": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
|
||||
"integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg=="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
|
||||
"optional": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz",
|
||||
"integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"buffer-shims": "1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"inherits": "2.0.3",
|
||||
"isarray": "1.0.0",
|
||||
"process-nextick-args": "1.0.7",
|
||||
"string_decoder": "1.0.3",
|
||||
"util-deprecate": "1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
||||
"optional": true
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"recast": {
|
||||
"version": "0.10.43",
|
||||
"resolved": "https://registry.npmjs.org/recast/-/recast-0.10.43.tgz",
|
||||
"integrity": "sha1-uV1Q9tYHYaX2JS4V2AZ4FoSRzn8=",
|
||||
"requires": {
|
||||
"ast-types": "0.8.15",
|
||||
"esprima-fb": "15001.1001.0-dev-harmony-fb",
|
||||
"private": "0.1.8",
|
||||
"source-map": "0.5.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"esprima-fb": {
|
||||
"version": "15001.1001.0-dev-harmony-fb",
|
||||
"resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz",
|
||||
"integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk="
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||
}
|
||||
}
|
||||
},
|
||||
"require-all": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/require-all/-/require-all-2.2.0.tgz",
|
||||
"integrity": "sha1-tEIMIzrAKC0P9Jsnf7iAqLXeCJQ="
|
||||
},
|
||||
"require_optional": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
|
||||
"integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"resolve-from": "2.0.0",
|
||||
"semver": "5.5.0"
|
||||
}
|
||||
},
|
||||
"resolve-from": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
|
||||
"integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=",
|
||||
"optional": true
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
||||
"integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
|
||||
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
|
||||
"optional": true
|
||||
},
|
||||
"simple-file-writer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-file-writer/-/simple-file-writer-2.0.0.tgz",
|
||||
"integrity": "sha1-4oPAoghoptQhnX7aGUwe9eK1gvo="
|
||||
},
|
||||
"snekfetch": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.1.tgz",
|
||||
"integrity": "sha512-aLEvf1YR440pINb0LEo/SL2Q2s/A26+YEqPlx09A0XpGH7qWp8iqIFFolVILHn2yudWXJne9QWyQu+lzDp+ksQ=="
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.1.31",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz",
|
||||
"integrity": "sha1-n3BNDWnZ4TioG63267T94z0VHGE=",
|
||||
"requires": {
|
||||
"amdefine": "1.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
|
||||
},
|
||||
"through2": {
|
||||
"version": "0.6.5",
|
||||
"resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
|
||||
"integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
|
||||
"requires": {
|
||||
"readable-stream": "1.0.34",
|
||||
"xtend": "4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "1.0.34",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
|
||||
"requires": {
|
||||
"core-util-is": "1.0.2",
|
||||
"inherits": "2.0.3",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "0.10.31"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.0.tgz",
|
||||
"integrity": "sha1-cT2LgY2kIGh0C/aDhtBHnmb8ins="
|
||||
},
|
||||
"ultron": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
|
||||
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz",
|
||||
"integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ="
|
||||
},
|
||||
"unreachable-branch-transform": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz",
|
||||
"integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo=",
|
||||
"requires": {
|
||||
"esmangle-evaluator": "1.0.1",
|
||||
"recast": "0.10.43",
|
||||
"through2": "0.6.5"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
"optional": true
|
||||
},
|
||||
"ws": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.2.tgz",
|
||||
"integrity": "sha512-t+WGpsNxhMR4v6EClXS8r8km5ZljKJzyGhJf7goJz9k5Ye3+b5Bvno5rjqPuIBn5mnn5GBb7o8IrIWHxX1qOLQ==",
|
||||
"requires": {
|
||||
"async-limiter": "1.0.0",
|
||||
"safe-buffer": "5.1.1",
|
||||
"ultron": "1.1.1"
|
||||
}
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
|
||||
}
|
||||
}
|
||||
}
|
26
core/package.json
Normal file
26
core/package.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@types/node": "8.9.1",
|
||||
"camo": "git+https://github.com/benji7425/camo.git#v0.12.4",
|
||||
"cron": "1.3.0",
|
||||
"discord.js": "11.2.0",
|
||||
"nedb": "1.8.0",
|
||||
"parent-package-json": "2.0.1",
|
||||
"require-all": "2.2.0",
|
||||
"simple-file-writer": "2.0.0"
|
||||
},
|
||||
"name": "discord-bot-core",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/benji7425/discord-bot-core.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/benji7425/discord-bot-core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/benji7425/discord-bot-core#readme",
|
||||
"description": "Core code shared amongst my Discord bots"
|
||||
}
|
74
core/util.js
Normal file
74
core/util.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
// @ts-ignore
|
||||
const InternalConfig = require("./internal-config.json");
|
||||
const Console = require("console");
|
||||
const SimpleFileWriter = require("simple-file-writer");
|
||||
|
||||
const logWriter = new SimpleFileWriter("./console.log");
|
||||
const debugLogWriter = new SimpleFileWriter("./debug.log");
|
||||
|
||||
function ask(client, textChannel, member, question) {
|
||||
//return a promise which will resolve once the user next sends a message in this textChannel
|
||||
return new Promise((resolve, reject) => {
|
||||
const cancelAsk = () => {
|
||||
client.removeListener("message", handler);
|
||||
textChannel.send("Response to question timed out");
|
||||
};
|
||||
|
||||
const askTimeout = setTimeout(cancelAsk, InternalConfig.askTimeout);
|
||||
|
||||
const handler = responseMessage => {
|
||||
if (responseMessage.channel.id === textChannel.id && responseMessage.member && responseMessage.member.id === member.id) {
|
||||
clearTimeout(askTimeout);
|
||||
resolve(responseMessage);
|
||||
}
|
||||
};
|
||||
|
||||
client.on("message", handler);
|
||||
|
||||
textChannel.send(member.toString() + " " + question).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function dateLog(...args) {
|
||||
doDateLog(Console.log, logWriter, args, "INFO");
|
||||
}
|
||||
|
||||
function dateError(...args) {
|
||||
doDateLog(Console.error, logWriter, args, "ERROR");
|
||||
}
|
||||
|
||||
function dateDebugError(...args) {
|
||||
doDateLog(null, null, args, "DEBUG ERROR");
|
||||
}
|
||||
|
||||
function dateDebug(...args) {
|
||||
doDateLog(null, null, args, "DEBUG");
|
||||
}
|
||||
|
||||
function doDateLog(consoleMethod, fileWriter, args, prefix = "") {
|
||||
args = formatArgs([`[${prefix}]`].concat(args));
|
||||
|
||||
if (consoleMethod !== null)
|
||||
consoleMethod.apply(this, args);
|
||||
|
||||
if (fileWriter !== null)
|
||||
fileWriter.write(formatArgsForFile(args));
|
||||
|
||||
debugLogWriter.write(formatArgsForFile(args));
|
||||
}
|
||||
|
||||
function formatArgs(args) {
|
||||
return [`[${new Date().toUTCString()}]`].concat(args);
|
||||
}
|
||||
|
||||
function formatArgsForFile(args) {
|
||||
return args.join(" ") + "\n";
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
dateError,
|
||||
dateLog,
|
||||
dateDebug,
|
||||
dateDebugError,
|
||||
ask
|
||||
};
|
1329
package-lock.json
generated
Normal file
1329
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
54
package.json
54
package.json
|
@ -1,28 +1,30 @@
|
|||
{
|
||||
"version": "3.0.0-b2",
|
||||
"main": "app/index.js",
|
||||
"scripts": {
|
||||
"start": "node bootstrap.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"discord.js": "11.1.0",
|
||||
"discordjs-util": "git+https://github.com/benji7425/discordjs-util.git",
|
||||
"feed-read": "0.0.1",
|
||||
"get-urls": "7.0.0",
|
||||
"jsonfile": "3.0.1",
|
||||
"shortid": "2.2.8"
|
||||
},
|
||||
"name": "discord-bot-rss-feed",
|
||||
"devDependencies": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/benji7425/discord-bot-rss-feed.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/benji7425/discord-bot-rss-feed/issues"
|
||||
},
|
||||
"homepage": "https://github.com/benji7425/discord-bot-rss-feed#readme",
|
||||
"description": ""
|
||||
"version": "3.5.2",
|
||||
"main": "app/index.js",
|
||||
"scripts": {
|
||||
"postinstall": "cd ./core && npm install",
|
||||
"start": "node ./app/index.js --name=rss-feed"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "9.3.0",
|
||||
"discord.js": "11.2.0",
|
||||
"eslint": "4.16.0",
|
||||
"get-urls": "7.0.0",
|
||||
"jsonfile": "3.0.1",
|
||||
"rss-parser": "2.12.0",
|
||||
"shortid": "2.2.8"
|
||||
},
|
||||
"name": "discord-bot-rss-feed",
|
||||
"devDependencies": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/benji7425/discord-bot-rss-feed.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/benji7425/discord-bot-rss-feed/issues"
|
||||
},
|
||||
"homepage": "https://github.com/benji7425/discord-bot-rss-feed#readme",
|
||||
"description": ""
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue