Browse Source

Remove now unused old v2 code

shorten
benji7425 4 years ago
parent
commit
84d28c371e
  1. 30
      .eslintrc.json
  2. 57
      .gitignore
  3. 3
      .npmrc
  4. 14
      .vscode/launch.json
  5. 114
      CHANGELOG.md
  6. 21
      LICENSE
  7. 1
      README.md
  8. 18
      app/config.json
  9. 152
      app/index.js
  10. 89
      app/models/feed-data.js
  11. 23
      app/models/guild-data.js
  12. 11
      docs/.gitrepo
  13. 3
      docs/home.md
  14. 19
      docs/user/commands.md
  15. 31
      docs/user/configuration.md
  16. 14
      docs/user/installation.md
  17. 10
      docs/user/troubleshooting.md
  18. 29
      package.json
  19. 20
      wrapper.js
  20. 30
      wrapper/.eslintrc.json
  21. 53
      wrapper/.gitignore
  22. 11
      wrapper/.gitrepo
  23. 3
      wrapper/.npmrc
  24. 101
      wrapper/README.md
  25. 93
      wrapper/index.js
  26. BIN
      wrapper/node_shrinkwrap/cjopus-0.0.4.tar
  27. BIN
      wrapper/node_shrinkwrap/discord.io-2.5.1.tar
  28. BIN
      wrapper/node_shrinkwrap/options-0.0.6.tar
  29. BIN
      wrapper/node_shrinkwrap/tweetnacl-0.14.5.tar
  30. BIN
      wrapper/node_shrinkwrap/ultron-1.0.2.tar
  31. BIN
      wrapper/node_shrinkwrap/ws-1.1.4.tar
  32. 36
      wrapper/npm-shrinkwrap.json
  33. 23
      wrapper/package.json

30
.eslintrc.json

@ -1,30 +0,0 @@
{
"env": {
"browser": true,
"node": true,
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"indent": [
"warn",
"tab",
{ "SwitchCase": 1}
],
"quotes": [
"warn",
"double"
],
"semi": [
"error",
"always"
],
"no-undef": "error",
"no-unused-vars": "warn",
"eqeqeq": ["error", "always"]
}
}

57
.gitignore

@ -1,57 +0,0 @@
### Discord bots ####
guilds.json
token.json
log
### Node ###
# Logs
logs
*.log
npm-debug.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
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
node_cache
jspm_packages
# 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
# End of https://www.gitignore.io/api/node

3
.npmrc

@ -1,3 +0,0 @@
save=true
save-exact=true
cache=node_cache

14
.vscode/launch.json

@ -1,14 +0,0 @@
{
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceRoot}/wrapper.js"
}
]
}

114
CHANGELOG.md

@ -1,114 +0,0 @@
# Changelog
## v2.0.0-b1
### Added
- Multi-guild support
- In-chat commands for setup and configuration
- Add a new feed
- View a list of feeds
- 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
### Updated
- !logsplease is now a developer command
- Subscriptions are now done using a role
- !subscribe and !unsibscribe add and remove the user from the role
- !sublist command is now removed
- 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
- New logging class to handle logging
### Updated
- Major refactor of a significant portion of the bot's code - should be easier to maintain now, but may have introduced some new bugs
- 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

21
LICENSE

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2016 Benji 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.

1
README.md

@ -1 +0,0 @@

18
app/config.json

@ -1,18 +0,0 @@
{
"feedCheckIntervalSec": 30,
"maxCacheSize": 10,
"generic": {
"saveFile": "./guilds.json",
"saveIntervalSec": 60,
"defaultDMResponse": "This bot does not have any handling for direct messages. To learn more or get help please visit http://benji7425.github.io, or join my Discord server here: https://discord.gg/SSkbwSJ"
},
"commands": {
"admin": {
"version": "version",
"addFeed": "add-feed",
"removeFeed": "remove-feed",
"viewFeeds": "view-feeds"
},
"anyone": {}
}
}

152
app/index.js

@ -1,152 +0,0 @@
//node imports
const FileSystem = require("fs");
//external lib imports
const JsonFile = require("jsonfile"); //for saving to/from JSON
const Url = require("url"); //for url parsing
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");
//global vars
let writeFile = null;
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
writeFile = () => JsonFile.writeFile(config.generic.saveFile, guildsData, err => { if (err) DiscordUtil.dateError("Error writing file", err); });
setInterval(() => writeFile(), config.generic.saveIntervalSec * 1000); //set interval to save data to file
parseLinksInGuilds(client.guilds, guildsData).then(() => writeFile(guildsData))
.then(() => checkFeedsInGuilds(client.guilds, guildsData))
.then(() => setInterval(() => checkFeedsInGuilds(client.guilds, guildsData), config.feedCheckIntervalSec * 1000)); //set up an interval to check all the feeds
//set up an on message handler to detect when links are posted
client.on("message", message => {
if (message.author.id !== client.user.id) { //check the bot isn't triggering itself
if (message.channel.type === "dm")
HandleMessage.dm(client, config, message);
else if (message.channel.type === "text" && message.member)
HandleMessage.text(client, config, message, guildsData);
}
});
};
const HandleMessage = {
dm: (client, config, message) => {
message.reply(config.generic.defaultDMResponse);
},
text: (client, config, message, guildsData) => {
//handle admins invoking commands
if (message.content.startsWith(message.guild.me.toString()) //user is @mention-ing the bot
&& message.member.permissions.has("ADMINISTRATOR")) //user has admin perms
{
const params = message.content.split(" "); //split the message at the spaces
//check which command was invoked
switch (params[1]) {
case config.commands.admin.version:
message.reply("v" + require("../package.json").version);
break;
case config.commands.admin.addFeed:
addFeed(client, guildsData, message, config.maxCacheSize);
break;
case config.commands.admin.removeFeed:
removeFeed(client, guildsData, message);
break;
case config.commands.admin.viewFeeds:
viewFeeds(client, guildsData[message.guild.id], message);
break;
}
}
else if (guildsData[message.guild.id]) {
guildsData[message.guild.id].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, guildsData, message, maxCacheSize) {
const feedUrl = [...GetUrls(message.content)][0];
const channel = message.mentions.channels.first();
if (!feedUrl || !channel)
return message.reply("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 (!guildsData[message.guild.id])
guildsData[message.guild.id] = new GuildData({ id: message.guild.id, feeds: [] });
guildsData[message.guild.id].feeds.push(feedData);
writeFile(guildsData);
responseMessage.reply("Your new feed has been saved!");
}
else
responseMessage.reply("Your feed has not been saved, please add it again with the correct details");
});
}
function removeFeed(client, guildsData, message) {
const parameters = message.content.split(" ");
if (parameters.length !== 3)
message.reply(`Please use the command as such:\n\`\`\` @${client.user.username} remove-feed feedid\`\`\``);
else {
const guildData = guildsData[message.guild.id];
const idx = guildData.feeds.findIndex(feed => feed.id === parameters[2]);
if (!Number.isInteger(idx))
message.reply("Can't find feed with id " + parameters[2]);
else {
guildData.feeds.splice(idx, 1);
writeFile(guildsData);
message.reply("Feed removed!");
}
}
}
function viewFeeds(client, guildData, message) {
if (!guildData)
return;
message.reply(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);
}
function fromJSON(json) {
const guildsData = Object.keys(json);
guildsData.forEach(guildID => { json[guildID] = new GuildData(json[guildID]); });
return json;
}

89
app/models/feed-data.js

@ -1,89 +0,0 @@
//my imports
const DiscordUtil = require("discordjs-util");
//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
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;
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);
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
};
}
/**
* 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);
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);
});
}
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
//if we don't have it cached already, cache it and callback
if (!this.cachedLinks.includes(latest)) {
this.cachedLinks.push(latest);
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);
}
}
});
});
}
toString() {
const blacklist = ["cachedLinks", "maxCacheSize"];
return `\`\`\`JavaScript\n ${JSON.stringify(this, (k, v) => !blacklist.includes(k) ? v : undefined, "\t")} \`\`\``;
}
};
function normaliseUrl(url) {
url = url.replace("https://", "http://"); //cheaty way to get around http and https not matching
if (Url.parse(url).host.includes("youtu")) //detect youtu.be and youtube.com - yes I know it's hacky
url = url.split("&")[0]; //quick way to chop off stuff like &feature=youtube
url = url.replace(/(www.)?youtube.com\/watch\?v=/, "youtu.be/"); //turn full url into share url
return url;
}

23
app/models/guild-data.js

@ -1,23 +0,0 @@
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));
}
cachePastPostedLinks(guild) {
const promises = [];
this.feeds.forEach(feed => {
promises.push(feed.updatePastPostedLinks(guild).catch(Util.dateError));
});
return Promise.all(promises);
}
checkFeeds(guilds) {
this.feeds.forEach(feed => feed.check(guilds.get(this.id)));
}
};

11
docs/.gitrepo

@ -1,11 +0,0 @@
; 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-feed-bot.wiki.git
branch = master
commit = f70e48b91074ad051eec48679d013409a79ed53e
parent = 21bc8bdaeb42f8eca6218fbe1910ddcc75d56c3c
cmdver = 0.3.1

3
docs/home.md

@ -1,3 +0,0 @@
If browsing on GitHub, please use the sidebar on the right to navigate the wiki.
Please use releases from the 'releases' page when installing rather than just cloning the repository yourself

19
docs/user/commands.md

@ -1,19 +0,0 @@
# Commands
There is a very basic level of interaction with the bot available via chat commands, all of which are prefixed with an exclamation mark '!'
## User commands
| command | action |
|--------------|-----------------------------------------------------------------------------------------|
| !help | list available commands |
| !subscribe | subscribe to feed notifications, so your username gets tagged when a new link is posted |
| !unsubscribe | unsubscribe from feed notifications |
## Developer commands
Requires your user ID to be specified in the developers list in config.json
| command | action |
|-------------|----------------------------|
| !logsplease | upload the log file |
| !cacheList | report all the cached URLs |

31
docs/user/configuration.md

@ -1,31 +0,0 @@
# Configuration
| parameter | description |
|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
| feedUrl | the RSS feed to check |
| channelID | report all the cached URLs |
| serverID | id of the server the bot is installed on - require to use subscriptions |
| pollingInterval | interval in ms to check the RSS feed |
| numLinksToCache | number of posted links to cache and check against before posting - turn this up if users post a lot of links in the channel |
| messageDeleteDelay | time in ms to leave response messages before deleting (eg "You have successfully subscribed") |
| youtubeMode | whether or not to convert YouTube links to their short url - recommended if you are pulling links from a YouTube channel feed |
| allowSubscriptions | whether or not to have the bot mention a role when it posts a link |
| subscribersRoleID | the ID of the role to mention when posting a link - you can find this by typing \@role in discord and copying out just the numeric part |
| developers | array of developer IDs - add a new one by putting a comma at the end of the one above and putting the ID in double quotes |
## How to find IDs
- Make sure developer mode is turned on in discord
- User Settings > Appearance > Developer Mode
| id | how to find |
|----------------------------|-----------------------------------------------|
| channelID | right click on the channel > copy ID |
| serverID | right click on the server name > copy ID |
| subscribersRoleID | type \@role in discord, copy the numeric part |
| user ID (to add developer) | right click on user > copy ID |
## Note about subsriptions
For subscriptions to work the bot needs the "Manage roles" permission, and needs to be in a role *higher than* the subscribers role

14
docs/user/installation.md

@ -1,14 +0,0 @@
# Installation
1. Make sure you have nodejs (v6+) and npm installed
2. Download the zip from [releases](https://github.com/benji7425/discord-feed-bot/releases) and extract
3. Open a terminal in extracted folder
4. Run `npm install` and wait for it to finish
5. Edit *config.json* to include your server/channel details (see configuration page for more info)
6. Create *bot-config.json* to include your bot token:
`{
"token": "abc123blahblahblahyourtokengoeshere"
}`
7. Run `node feed-bot.js`
See configuration page for more detail on configuration options

10
docs/user/troubleshooting.md

@ -1,10 +0,0 @@
# Troubleshooting
## Bot can't add users to role
- Make sure the bot's role has the "Manage Roles" permission
- Make sure the bot's role is higher than the subscribers role
## Other stuff
Feel free to contact me at the email on my GitHub profile :)

29
package.json

@ -1,29 +0,0 @@
{
"name": "discord-bot-feed-linker",
"version": "2.1.0-b1",
"description": "",
"main": "app/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node wrapper.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/benji7425/discord-bot-feed-linker.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/benji7425/discord-bot-feed-linker/issues"
},
"homepage": "https://github.com/benji7425/discord-bot-feed-linker#readme",
"dependencies": {
"discord.js": "11.1.0",
"discordjs-util": "git+https://github.com/benji7425/discordjs-util.git",
"dns": "0.2.2",
"feed-read": "0.0.1",
"get-urls": "7.0.0",
"jsonfile": "3.0.1",
"shortid": "2.2.8"
}
}

20
wrapper.js

@ -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.setGame("benji7425.github.io");
});
client.on("disconnect", eventData => {
DiscordUtil.dateError("Bot was disconnected!", eventData.code, eventData.reason);
});

30
wrapper/.eslintrc.json

@ -1,30 +0,0 @@
{
"env": {
"browser": true,
"node": true,
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"indent": [
"warn",
"tab",
{ "SwitchCase": 1}
],
"quotes": [
"warn",
"double"
],
"semi": [
"error",
"always"
],
"no-undef": "error",
"no-unused-vars": "warn",
"eqeqeq": ["error", "always"]
}
}

53
wrapper/.gitignore

@ -1,53 +0,0 @@
# Project specific
token.json
### Node ###
# Logs
logs
*.log
npm-debug.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
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_cache
node_modules
jspm_packages
# 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

11
wrapper/.gitrepo

@ -1,11 +0,0 @@
; 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/shell-discord-bot.git
branch = master
commit = 0cf920f3a659f11d388a62b248073c08bf89949b
parent = 8fc554c68d7f249ca5c7307e78d81866cb130e74
cmdver = 0.3.1

3
wrapper/.npmrc

@ -1,3 +0,0 @@
save=true
save-exact=true
cache=node_cache

101
wrapper/README.md

@ -1,101 +0,0 @@
# Shell Discord bot
The purpose of this is to act as a shell for other bot modules, so that a single bot user account can be used for a multi-function bot.
## Usage
### As a project base
- Fork/clone/merge this repo into a new one
- Run `npm init` to re-initialise as the new repo
- Run `npm install`
- Create *token.json* with your discord token: `{ "token": "1234567890" }`
- Add your modules and reference them in `var BotModules = [];`
for example: `var BotModules = [require("my-module")];`
### As a wrapper
- Use [git subrepo](https://github.com/ingydotnet/git-subrepo) to clone this into a folder called *wrapper* in your parent project
- Update your parent project's `start` script to run `node wrapper/index.js`
- Add a reference to your parent project's main file in `var BotModules = [];`
for example: `var BotModules = [require("../app/index.js")];`
## Creating a bot module
Interfacing with each bot module is done by the properties in its module.exports. Available properties are:
| property | property type | parameters | description |
|--------------|---------------|---------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| onReady | method | bot | called on the bot ready event |
| onDisconnect | method | none | called when the bot disconnects |
| onMessage | method | bot, user, userID, channelID, message | called when the bot receives a message - identical to the 'action' property of a command, but triggered on every message (see below) |
| commands | array | N/A | commands a user can invoke - like the onMessage event, but only triggered on expected commands (see below) |
### Commands
A command object contains a command, and an action to invoke if the message contains that command. Each command object needs a certain set of properties:
| property | optional? | value | description |
|------------|-----------|--------------------------|-----------------------------------------------------------------------------------------------------------|
| command | required | string | Command to look for in the message |
| type | required | "equals" or "startsWith" | Describes whether we are looking for the message to be the exact command, or just to start with it |
| action | required | function | Callback to invoke if the command is matched (see below) |
| channelIDs | optional | array of strings | If this property is present, the command will only be triggered if sent in one of these channels |
| roleIDs | optional | array of strings | If this property is present, the command will only be triggered if send by a user with one of these roles |
| userIDs | optional | array of strings | If this property is present, the command will only be triggered if sent by one of these users |
**Permission heirarchy**
channelIDs > roleIDs > userIDs
Examples of commands that *won't* be triggered:
- channelIDs *contains* the channel, but userIDs *doesn't* - channelID check will pass, userID check subsequently won't
- channelIDs *doesn't contain* the channel, roleIDs or userIDs *contain* the user - channelID check will block the command, regardless of other permissions
- channelIDs *contains* the channel, roleIDs *doesn't* contain the user's roles, userIDs *contains* the user - channelID check will pass, roleID check will block
#### Actions
An action is a callback function called if the specified command is found. It must take these parameters:
| parameter | type | description |
|-----------|--------|----------------------------------------------------------------------|
| bot | object | The [discord.io](https://github.com/izy521/discord.io) client object |
| user | string | Username of the user who sent the message |
| userID | string | User ID of the user who sent the message |
| channelID | string | Channel ID of the channel the message was sent in |
| message | string | The message/command that was sent |
Example 1:
```JavaScript
{
command: "ping",
type: "equals",
action: (bot, user, userID, channelID, message) => {
bot.sendMessage({
to: channelID,
message: "pong"
})
},
userIDs: ["1234567890"]
}
```
The above example will only call *action* if the user with ID 1234567890 sends "ping"
Example 2:
```JavaScript
{
command: "!define",
type: "startsWith",
action: (bot, user, userID, channelID, message) => {
bot.sendMessage({
to: channelID,
message: getDefinition(message.split(" ")[1])
})
}
}
```
The above example expects the user to type '!define something', ie only checking for the message to start with '!define'. You are still passed the full message, so can split it up and read it however you want.
*action* will only be called if the message begins with '!define', but has no restrictions on which channel(s) or user(s) use it

93
wrapper/index.js

@ -1,93 +0,0 @@
//node imports
const Console = require("console");
//external module imports
var Discord = require("discord.io");
var BotModules = [require("../app/index.js")()];
var bot;
var EventHandlers = {
onReady: () => {
Console.info("Registered bot " + bot.username + " with id " + bot.id);
for (let i = 0, len = BotModules.length; i < len; i++) {
let botModule = BotModules[i];
if (botModule.onReady) botModule.onReady(bot);
}
},
onDisconnect: (err, code) => {
Console.error("Bot was disconnected!", err, code);
for (let i = 0, len = BotModules.length; i < len; i++) {
let botModule = BotModules[i];
if (botModule.onDisconnect) botModule.onDisconnect();
}
bot.connect();
},
onMessage: (user, userID, channelID, message) => {
for (let i = 0, iLen = BotModules.length; i < iLen; i++) {
let botModule = BotModules[i];
if (botModule.commands) {
for (let j = 0, jLen = botModule.commands.length; j < jLen; j++) {
let messageTrigger = botModule.commands[j];
if (commandIsAllowed(messageTrigger, user, userID, channelID))
switch (messageTrigger.type) {
case "startsWith":
if (message.startsWith(messageTrigger.command))
messageTrigger.action(bot, user, userID, channelID, message);
break;
default:
if (message === messageTrigger.command)
messageTrigger.action(bot, user, userID, channelID, message);
}
}
}
if (botModule.onMessage) botModule.onMessage(bot, user, userID, channelID, message);
}
}
};
var commandIsAllowed = (messageTrigger, user, userID, channelID) => {
//if we aren't allowed this command in this channel, disallow the command
if (messageTrigger.channelIDs && !messageTrigger.channelIDs.includes(channelID))
return false;
if (messageTrigger.roleIDs) { //check if we have a role constraint
var userHasPermissiveRole = false;
messageTrigger.roleIDs.forEach((element) => { //iterate over all the allowed role IDs
if (userHasRole(userID, channelID, element)) userHasPermissiveRole = true; //check if the user has this role
});
if (!userHasPermissiveRole) return false; //disallow the command if the user doesn't have one of these role IDs
}
//if this user isn't allowed, disallow the command
if (messageTrigger.userIDs && !messageTrigger.userIDs.includes(userID))
return false;
//if we haven't returned false by now, then the command is allowed
return true;
};
var userHasRole = (userID, channelID, roleID) => {
var userRoles = bot.servers[bot.channels[channelID].guild_id].members[userID].roles;
return userRoles.includes(roleID);
};
(() => {
bot = new Discord.Client({
token: require("./token.json").token,
autorun: true
});
bot.on("ready", EventHandlers.onReady);
bot.on("disconnect", EventHandlers.onDisconnect);
bot.on("message", EventHandlers.onMessage);
})();

BIN
wrapper/node_shrinkwrap/cjopus-0.0.4.tar

BIN
wrapper/node_shrinkwrap/discord.io-2.5.1.tar

BIN
wrapper/node_shrinkwrap/options-0.0.6.tar

BIN
wrapper/node_shrinkwrap/tweetnacl-0.14.5.tar

BIN
wrapper/node_shrinkwrap/ultron-1.0.2.tar

BIN
wrapper/node_shrinkwrap/ws-1.1.4.tar

36
wrapper/npm-shrinkwrap.json

@ -1,36 +0,0 @@
{
"name": "discord-bot",
"version": "1.0.0",
"dependencies": {
"cjopus": {
"version": "0.0.4",
"from": "cjopus@>=0.0.4 <0.0.5",
"resolved": "./node_shrinkwrap/cjopus-0.0.4.tar"
},
"discord.io": {
"version": "2.5.1",
"from": "discord.io@latest",
"resolved": "./node_shrinkwrap/discord.io-2.5.1.tar"
},
"options": {
"version": "0.0.6",
"from": "options@>=0.0.5",
"resolved": "./node_shrinkwrap/options-0.0.6.tar"
},
"tweetnacl": {
"version": "0.14.5",
"from": "tweetnacl@>=0.14.0 <0.15.0",
"resolved": "./node_shrinkwrap/tweetnacl-0.14.5.tar"
},
"ultron": {
"version": "1.0.2",
"from": "ultron@>=1.0.0 <1.1.0",
"resolved": "./node_shrinkwrap/ultron-1.0.2.tar"
},
"ws": {
"version": "1.1.4",
"from": "ws@>=1.1.0 <2.0.0",
"resolved": "./node_shrinkwrap/ws-1.1.4.tar"
}
}
}

23
wrapper/package.json

@ -1,23 +0,0 @@
{
"name": "discord-bot",
"version": "1.0.0",
"description": "The purpose of this is to act as a shell for other bot modules, so that a single bot user account can be used for a multi-function bot.",
"main": "index.js",
"dependencies": {
"discord.io": "2.5.1"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/benji7425/discord-bot-wrapper.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/benji7425/discord-bot-wrapper/issues"
},
"homepage": "https://github.com/benji7425/discord-bot-wrapper#readme"
}
Loading…
Cancel
Save