Update formatting; convert tabs to 4x spaces

I believe this is the accepted method and should increase default editor compatibility
This commit is contained in:
benji7425 2018-01-26 23:47:23 +00:00
parent 381ff1d219
commit 672c07f5b0
9 changed files with 233 additions and 233 deletions

View File

@ -9,43 +9,43 @@ const ShortID = require("shortid");
const readFeed = url => promisify(require("rss-parser").parseURL)(url); const readFeed = url => promisify(require("rss-parser").parseURL)(url);
module.exports = new Core.Command({ module.exports = new Core.Command({
name: "add-feed", name: "add-feed",
description: "Add an RSS feed to be posted in a channel, with an optional role to tag", description: "Add an RSS feed to be posted in a channel, with an optional role to tag",
syntax: "add-feed <url> <#channel> [@role]", syntax: "add-feed <url> <#channel> [@role]",
admin: true, admin: true,
invoke: invoke invoke: invoke
}); });
function invoke({ message, params, guildData, client }) { function invoke({ message, params, guildData, client }) {
const feedUrl = [...GetUrls(message.content)][0], const feedUrl = [...GetUrls(message.content)][0],
channel = message.mentions.channels.first(); channel = message.mentions.channels.first();
if (!feedUrl || !channel) if (!feedUrl || !channel)
return Promise.reject("Please provide both a channel and an RSS feed URL. You can optionally @mention a role also."); 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(), const role = message.mentions.roles.first(),
feedData = FeedData.create({ feedData = FeedData.create({
feedID: ShortID.generate(), feedID: ShortID.generate(),
url: feedUrl, url: feedUrl,
channelID: channel.id, channelID: channel.id,
roleID: role ? role.id : null, roleID: role ? role.id : null,
maxCacheSize: Config.maxCacheSize maxCacheSize: Config.maxCacheSize
}); });
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
readFeed(feedUrl) readFeed(feedUrl)
.then(() => { .then(() => {
Core.util.ask(client, message.channel, message.member, "Are you happy with this (yes/no)?\n" + feedData.toString()) Core.util.ask(client, message.channel, message.member, "Are you happy with this (yes/no)?\n" + feedData.toString())
.then(responseMessage => { .then(responseMessage => {
if (responseMessage.content.toLowerCase() === "yes") { if (responseMessage.content.toLowerCase() === "yes") {
guildData.feeds.push(feedData); guildData.feeds.push(feedData);
guildData.cachePastPostedLinks(message.guild) guildData.cachePastPostedLinks(message.guild)
.then(() => resolve("Your new feed has been saved!")); .then(() => resolve("Your new feed has been saved!"));
} }
else else
reject("Your feed has not been saved, please add it again with the correct details"); 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}`)); .catch(err => reject(`Unable to add the feed due to the following error:\n${err.message}`));
}); });
} }

View File

@ -1,18 +1,18 @@
const Core = require("../../discord-bot-core"); const Core = require("../../discord-bot-core");
module.exports = new Core.Command({ module.exports = new Core.Command({
name: "remove-feed", name: "remove-feed",
description: "Remove an RSS feed by it's ID", description: "Remove an RSS feed by it's ID",
syntax: "remove-feed <dir>", syntax: "remove-feed <dir>",
admin: true, admin: true,
invoke: invoke invoke: invoke
}); });
function invoke({ message, params, guildData, client }) { function invoke({ message, params, guildData, client }) {
const idx = guildData.feeds.findIndex(feed => feed.feedID === params[0]); const idx = guildData.feeds.findIndex(feed => feed.feedID === params[0]);
if (!Number.isInteger(idx)) if (!Number.isInteger(idx))
return Promise.reject("Can't find feed with id " + params[0]); return Promise.reject("Can't find feed with id " + params[0]);
guildData.feeds.splice(idx, 1); guildData.feeds.splice(idx, 1);
return Promise.resolve("Feed removed!"); return Promise.resolve("Feed removed!");
} }

View File

@ -2,22 +2,22 @@ const Core = require("../../discord-bot-core");
const Config = require("../config.json"); const Config = require("../config.json");
module.exports = new Core.Command({ module.exports = new Core.Command({
name: "view-feeds", name: "view-feeds",
description: "View a list of configured feeds and their associated details", description: "View a list of configured feeds and their associated details",
syntax: "view-feed", syntax: "view-feed",
admin: true, admin: true,
invoke: invoke invoke: invoke
}); });
function invoke({ message, params, guildData, client }) { function invoke({ message, params, guildData, client }) {
if (!guildData) if (!guildData)
return Promise.reject("Guild not setup"); return Promise.reject("Guild not setup");
const startIdx = params[0] ? (params[0] - 1) * Config.viewFeedsPaginationLimit : 0; const startIdx = params[0] ? (params[0] - 1) * Config.viewFeedsPaginationLimit : 0;
const endIdx = startIdx + Config.viewFeedsPaginationLimit + 1; const endIdx = startIdx + Config.viewFeedsPaginationLimit + 1;
let responseStr = guildData.feeds.map(f => f.toString()).slice(startIdx, endIdx).join("\n"); let responseStr = guildData.feeds.map(f => f.toString()).slice(startIdx, endIdx).join("\n");
if (guildData.feeds.length > endIdx) if (guildData.feeds.length > endIdx)
responseStr += `Use *view-feeds ${startIdx + 2}* to view more`; responseStr += `Use *view-feeds ${startIdx + 2}* to view more`;
return Promise.resolve(responseStr || "No feeds configured"); return Promise.resolve(responseStr || "No feeds configured");
} }

View File

@ -5,70 +5,70 @@ const GuildData = require("./models/guild-data.js");
const Config = require("./config.json"); const Config = require("./config.json");
const guildsIterator = (function* () { const guildsIterator = (function* () {
while (true) { while (true) {
if (client.guilds.size === 0) if (client.guilds.size === 0)
yield null; yield null;
else else
for (let i = 0; i < client.guilds.size; i++) for (let i = 0; i < client.guilds.size; i++)
yield [...client.guilds.values()][i]; yield [...client.guilds.values()][i];
} }
})(); })();
// @ts-ignore // @ts-ignore
const client = new Core.Client(require("../token.json"), __dirname + "/commands", GuildData); const client = new Core.Client(require("../token.json"), __dirname + "/commands", GuildData);
client.on("beforeLogin", () => client.on("beforeLogin", () =>
setInterval(doGuildIteration, Config.feedCheckInterval)); setInterval(doGuildIteration, Config.feedCheckInterval));
client.on("ready", () => { client.on("ready", () => {
parseLinksInGuilds().then(doGuildIteration); parseLinksInGuilds().then(doGuildIteration);
require("./legacy-upgrader.js")(); //upgrade legacy json into new database format require("./legacy-upgrader.js")(); //upgrade legacy json into new database format
}); });
client.on("message", message => { client.on("message", message => {
if (message.channel.type !== "text" || !message.member) if (message.channel.type !== "text" || !message.member)
return; return;
client.guildDataModel.findOne({ guildID: message.guild.id }) client.guildDataModel.findOne({ guildID: message.guild.id })
.then(guildData => guildData && cacheUrlsInMessage(message, guildData)); .then(guildData => guildData && cacheUrlsInMessage(message, guildData));
}); });
client.bootstrap(); client.bootstrap();
//INTERNAL FUNCTIONS// //INTERNAL FUNCTIONS//
function parseLinksInGuilds() { function parseLinksInGuilds() {
const promises = []; const promises = [];
client.guildDataModel.find().then(guildDatas => client.guildDataModel.find().then(guildDatas =>
guildDatas guildDatas
.filter(guildData => client.guilds.get(guildData.guildID)) .filter(guildData => client.guilds.get(guildData.guildID))
.map(guildData => ({ guildData, guild: client.guilds.get(guildData.guildID) })) .map(guildData => ({ guildData, guild: client.guilds.get(guildData.guildID) }))
.forEach(({ guildData, guild }) => promises.push(guildData.cachePastPostedLinks(guild).catch())) .forEach(({ guildData, guild }) => promises.push(guildData.cachePastPostedLinks(guild).catch()))
); );
return Promise.all(promises); return Promise.all(promises);
} }
function doGuildIteration() { function doGuildIteration() {
const guild = guildsIterator.next().value; const guild = guildsIterator.next().value;
if (guild) if (guild)
client.guildDataModel.findOne({ guildID: guild.id }) client.guildDataModel.findOne({ guildID: guild.id })
.then(guildData => guildData && checkGuildFeeds(guild, guildData)); .then(guildData => guildData && checkGuildFeeds(guild, guildData));
} }
function checkGuildFeeds(guild, guildData) { function checkGuildFeeds(guild, guildData) {
guildData.checkFeeds(guild) guildData.checkFeeds(guild)
.then(values => values.some(x => x) && guildData.save()); .then(values => values.some(x => x) && guildData.save());
} }
function cacheUrlsInMessage(message, guildData) { function cacheUrlsInMessage(message, guildData) {
const anyNewLinksPosted = []; const anyNewLinksPosted = [];
guildData.feeds guildData.feeds
.filter(feedData => message.channel.id === feedData.channelID) .filter(feedData => message.channel.id === feedData.channelID)
.forEach(feedData => anyNewLinksPosted.push(feedData.cache(...GetUrls(message.content)))); .forEach(feedData => anyNewLinksPosted.push(feedData.cache(...GetUrls(message.content))));
if (anyNewLinksPosted.some(x => x)) if (anyNewLinksPosted.some(x => x))
guildData.save(); guildData.save();
} }

View File

@ -4,27 +4,27 @@ const NewFeedData = require("./models/feed-data.js");
const FileSystem = require("fs"); const FileSystem = require("fs");
module.exports = function () { module.exports = function () {
if (!FileSystem.existsSync("./guilds.json")) if (!FileSystem.existsSync("./guilds.json"))
return; return;
const legacyJson = require("../guilds.json"); const legacyJson = require("../guilds.json");
for (let guildID of Object.keys(legacyJson)) { for (let guildID of Object.keys(legacyJson)) {
const guildData = NewGuildData.create({ guildID }); const guildData = NewGuildData.create({ guildID });
for (let feed of legacyJson[guildID].feeds) { for (let feed of legacyJson[guildID].feeds) {
guildData.feeds.push(NewFeedData.create({ guildData.feeds.push(NewFeedData.create({
feedID: feed.id, feedID: feed.id,
url: feed.url, url: feed.url,
roleID: feed.roleID, roleID: feed.roleID,
channelID: feed.channelID, channelID: feed.channelID,
cachedLinks: feed.cachedLinks, cachedLinks: feed.cachedLinks,
maxCacheSize: feed.maxCacheSize maxCacheSize: feed.maxCacheSize
})); }));
} }
guildData.save(); guildData.save();
} }
FileSystem.rename("./guilds.json", "./guilds.json.backup"); FileSystem.rename("./guilds.json", "./guilds.json.backup");
}; };

View File

@ -13,127 +13,127 @@ const readFeed = url => promisify(require("rss-parser").parseURL)(url);
const resolveDns = promisify(require("dns").resolve); const resolveDns = promisify(require("dns").resolve);
module.exports = class FeedData extends Core.BaseEmbeddedData { module.exports = class FeedData extends Core.BaseEmbeddedData {
constructor() { constructor() {
super(); super();
this.feedID = ""; this.feedID = "";
this.url = ""; this.url = "";
this.channelID = ""; this.channelID = "";
this.roleID = ""; this.roleID = "";
this.cachedLinks = []; this.cachedLinks = [];
this.maxCacheSize = 100; this.maxCacheSize = 100;
// @ts-ignore // @ts-ignore
this.schema({ this.schema({
feedID: String, feedID: String,
url: String, url: String,
channelID: String, channelID: String,
roleID: String, roleID: String,
cachedLinks: [String], cachedLinks: [String],
maxCacheSize: Number maxCacheSize: Number
}); });
} }
cache(...elements) { cache(...elements) {
const newArticles = elements const newArticles = elements
.map(el => normaliseUrlForCache(el)) .map(el => normaliseUrlForCache(el))
.filter(el => !this._isCached(el)); .filter(el => !this._isCached(el));
Array.prototype.push.apply(this.cachedLinks, newArticles); Array.prototype.push.apply(this.cachedLinks, newArticles);
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 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
return elements.length > 0; return elements.length > 0;
} }
updatePastPostedLinks(guild) { updatePastPostedLinks(guild) {
const channel = guild.channels.get(this.channelID); const channel = guild.channels.get(this.channelID);
if (!channel) if (!channel)
return Promise.reject("Channel not found!"); return Promise.reject("Channel not found!");
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
channel.fetchMessages({ limit: 100 }) channel.fetchMessages({ limit: 100 })
.then(messages => { .then(messages => {
/* we want to push the links in oldest first, but discord.js returns messages newest first, so we need to reverse them /* 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 */ * 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))); [...messages.values()].reverse().forEach(m => this.cache(...GetUrls(m.content)));
resolve(); resolve();
}) })
.catch(reject); .catch(reject);
}); });
} }
fetchLatest(guild) { fetchLatest(guild) {
const dnsPromise = resolveDns(Url.parse(this.url).host).then(() => this._doFetchRSS(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)); dnsPromise.catch(err => DiscordUtil.dateDebugError("Connection error: Can't resolve host", err.message || err));
return dnsPromise; return dnsPromise;
} }
toString() { toString() {
const blacklist = ["cachedLinks", "maxCacheSize"]; const blacklist = ["cachedLinks", "maxCacheSize"];
return `\`\`\`JavaScript\n ${JSON.stringify(this, (k, v) => !blacklist.find(x => x === k) ? v : undefined, "\t")} \`\`\``; return `\`\`\`JavaScript\n ${JSON.stringify(this, (k, v) => !blacklist.find(x => x === k) ? v : undefined, "\t")} \`\`\``;
} }
_isCached(url){ _isCached(url) {
return this.cachedLinks.indexOf(normaliseUrlForCache(url)) > -1; return this.cachedLinks.indexOf(normaliseUrlForCache(url)) > -1;
} }
_doFetchRSS(guild) { _doFetchRSS(guild) {
const feedPromise = readFeed(this.url).then(parsed => this._processLatestArticle(guild, parsed.feed.entries)); const feedPromise = readFeed(this.url).then(parsed => this._processLatestArticle(guild, parsed.feed.entries));
feedPromise.catch(err => DiscordUtil.dateDebugError([`Error reading feed ${this.url}`, err])); feedPromise.catch(err => DiscordUtil.dateDebugError([`Error reading feed ${this.url}`, err]));
return feedPromise; return feedPromise;
} }
_processLatestArticle(guild, entries) { _processLatestArticle(guild, entries) {
if (entries.length === 0 || !entries[0].link) if (entries.length === 0 || !entries[0].link)
return false; return false;
if (this._isCached(entries[0].link)) if (this._isCached(entries[0].link))
return false; return false;
this.cache(entries[0].link); this.cache(entries[0].link);
const channel = guild.channels.get(this.channelID), const channel = guild.channels.get(this.channelID),
role = guild.roles.get(this.roleID); role = guild.roles.get(this.roleID);
channel.send((role || "") + formatPost(entries[0])) channel.send((role || "") + formatPost(entries[0]))
.catch(err => DiscordUtil.dateDebugError(`Error posting in ${channel.id}: ${err.message || err}`)); .catch(err => DiscordUtil.dateDebugError(`Error posting in ${channel.id}: ${err.message || err}`));
return true; return true;
} }
}; };
function formatPost(article) { function formatPost(article) {
let message = ""; let message = "";
if (article.title) message += `\n**${article.title}**`; if (article.title) message += `\n**${article.title}**`;
if (article.content) message += article.content.length > Config.charLimit ? "\nArticle content too long for a single Discord message!" : `\n${article.content}`; if (article.content) message += article.content.length > Config.charLimit ? "\nArticle content too long for a single Discord message!" : `\n${article.content}`;
if (article.link) message += `\n\n${normaliseUrlForDiscord(article.link)}`; if (article.link) message += `\n\n${normaliseUrlForDiscord(article.link)}`;
return message; return message;
} }
function normaliseUrlForDiscord(url) { function normaliseUrlForDiscord(url) {
const parsedUrl = Url.parse(url); const parsedUrl = Url.parse(url);
if (parsedUrl.host && parsedUrl.host.includes("youtube.com")) if (parsedUrl.host && parsedUrl.host.includes("youtube.com"))
url = normaliseYouTubeUrl(url, parsedUrl); url = normaliseYouTubeUrl(url, parsedUrl);
return url; return url;
} }
function normaliseYouTubeUrl(origUrl, parsedUrl) { function normaliseYouTubeUrl(origUrl, parsedUrl) {
const videoIDParam = parsedUrl.query ? parsedUrl.query.split("&").find(x => x.startsWith("v=")) : null; const videoIDParam = parsedUrl.query ? parsedUrl.query.split("&").find(x => x.startsWith("v=")) : null;
if (!videoIDParam) if (!videoIDParam)
return origUrl; return origUrl;
const videoID = videoIDParam.substring(videoIDParam.indexOf("=") + 1, videoIDParam.length); const videoID = videoIDParam.substring(videoIDParam.indexOf("=") + 1, videoIDParam.length);
return `http://youtu.be/${videoID}` return `http://youtu.be/${videoID}`
} }
function normaliseUrlForCache(url){ function normaliseUrlForCache(url) {
return normaliseUrlForDiscord(url).replace(/^((https?:\/\/)?(www.)?)/, ""); return normaliseUrlForDiscord(url).replace(/^((https?:\/\/)?(www.)?)/, "");
} }

View File

@ -2,33 +2,33 @@ const Core = require("../../discord-bot-core");
const FeedData = require("./feed-data.js"); const FeedData = require("./feed-data.js");
module.exports = class GuildData extends Core.BaseGuildData { module.exports = class GuildData extends Core.BaseGuildData {
constructor() { constructor() {
super(); super();
this.feeds = []; this.feeds = [];
this.schema({ this.schema({
feeds: [FeedData] feeds: [FeedData]
}); });
} }
cachePastPostedLinks(guild) { cachePastPostedLinks(guild) {
return Promise.all( return Promise.all(
this.feeds this.feeds
.filter(feed => feedIsActive(feed, guild)) .filter(feed => feedIsActive(feed, guild))
.map(feed => feed.updatePastPostedLinks(guild).catch(err => null)) .map(feed => feed.updatePastPostedLinks(guild).catch(err => null))
); );
} }
checkFeeds(guild) { checkFeeds(guild) {
return Promise.all( return Promise.all(
this.feeds this.feeds
.filter(feed => feedIsActive(feed, guild)) .filter(feed => feedIsActive(feed, guild))
.map(feed => feed.fetchLatest(guild).catch(err => null)) .map(feed => feed.fetchLatest(guild).catch(err => null))
); );
} }
}; };
function feedIsActive(feed, guild) { function feedIsActive(feed, guild) {
return guild.channels.get(feed.channelID); return guild.channels.get(feed.channelID);
} }

View File

@ -4,27 +4,27 @@ const InternalConfig = require("../internal-config.json");
const ParentPackageJson = require("../../package.json"); const ParentPackageJson = require("../../package.json");
module.exports = new Command({ module.exports = new Command({
name: "help", name: "help",
description: "Display available commands with descriptions", description: "Display available commands with descriptions",
syntax: "help", syntax: "help",
admin: false, admin: false,
invoke invoke
}); });
function invoke({ commands, isMemberAdmin }) { function invoke({ commands, isMemberAdmin }) {
return Promise.resolve(createHelpEmbed(ParentPackageJson.name, commands, isMemberAdmin)); return Promise.resolve(createHelpEmbed(ParentPackageJson.name, commands, isMemberAdmin));
} }
function createHelpEmbed(name, commands, userIsAdmin) { function createHelpEmbed(name, commands, userIsAdmin) {
const commandsArr = Object.keys(commands).map(x => commands[x]).filter(x => userIsAdmin || !x.admin); 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__`); const embed = new Discord.RichEmbed().setTitle(`__${(ParentPackageJson.name + "").replace("discord-bot-", "")} help__`);
commandsArr.forEach(command => { commandsArr.forEach(command => {
embed.addField(command.name, `${command.description}\n**Usage:** *${name} ${command.syntax}*${userIsAdmin && command.admin ? "\n***Admin only***" : ""}`); embed.addField(command.name, `${command.description}\n**Usage:** *${name} ${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); embed.addField("__Need more help?__", `[Visit my website](${InternalConfig.website}) or [Join my Discord](${InternalConfig.discordInvite})`, true);
return { embed }; return { embed };
} }

View File

@ -2,13 +2,13 @@ const Command = require("../command.js");
const ParentPackageJson = require("../../package.json"); const ParentPackageJson = require("../../package.json");
module.exports = new Command({ module.exports = new Command({
name: "version", name: "version",
description: "Return version number", description: "Return version number",
syntax: "version", syntax: "version",
admin: false, admin: false,
invoke invoke
}); });
function invoke() { function invoke() {
return Promise.resolve(ParentPackageJson.version); return Promise.resolve(ParentPackageJson.version);
} }