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);
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
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();
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.");
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
});
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}`));
});
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}`));
});
}

View File

@ -1,18 +1,18 @@
const Core = require("../../discord-bot-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
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]);
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!");
guildData.feeds.splice(idx, 1);
return Promise.resolve("Feed removed!");
}

View File

@ -2,22 +2,22 @@ const Core = require("../../discord-bot-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
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");
if (!guildData)
return Promise.reject("Guild not setup");
const startIdx = params[0] ? (params[0] - 1) * Config.viewFeedsPaginationLimit : 0;
const endIdx = startIdx + Config.viewFeedsPaginationLimit + 1;
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");
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");
}

View File

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

View File

@ -4,27 +4,27 @@ const NewFeedData = require("./models/feed-data.js");
const FileSystem = require("fs");
module.exports = function () {
if (!FileSystem.existsSync("./guilds.json"))
return;
if (!FileSystem.existsSync("./guilds.json"))
return;
const legacyJson = require("../guilds.json");
const legacyJson = require("../guilds.json");
for (let guildID of Object.keys(legacyJson)) {
const guildData = NewGuildData.create({ guildID });
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
}));
}
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();
}
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);
module.exports = class FeedData extends Core.BaseEmbeddedData {
constructor() {
super();
constructor() {
super();
this.feedID = "";
this.url = "";
this.channelID = "";
this.roleID = "";
this.cachedLinks = [];
this.maxCacheSize = 100;
this.feedID = "";
this.url = "";
this.channelID = "";
this.roleID = "";
this.cachedLinks = [];
this.maxCacheSize = 100;
// @ts-ignore
this.schema({
feedID: String,
url: String,
channelID: String,
roleID: String,
cachedLinks: [String],
maxCacheSize: Number
});
}
// @ts-ignore
this.schema({
feedID: String,
url: String,
channelID: String,
roleID: String,
cachedLinks: [String],
maxCacheSize: Number
});
}
cache(...elements) {
const newArticles = elements
.map(el => normaliseUrlForCache(el))
.filter(el => !this._isCached(el));
cache(...elements) {
const newArticles = elements
.map(el => normaliseUrlForCache(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) {
const channel = guild.channels.get(this.channelID);
updatePastPostedLinks(guild) {
const channel = guild.channels.get(this.channelID);
if (!channel)
return Promise.reject("Channel not found!");
if (!channel)
return Promise.reject("Channel not found!");
return new Promise((resolve, reject) => {
channel.fetchMessages({ limit: 100 })
.then(messages => {
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);
});
}
[...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));
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));
dnsPromise.catch(err => DiscordUtil.dateDebugError("Connection error: Can't resolve host", err.message || err));
return dnsPromise;
}
return dnsPromise;
}
toString() {
const blacklist = ["cachedLinks", "maxCacheSize"];
return `\`\`\`JavaScript\n ${JSON.stringify(this, (k, v) => !blacklist.find(x => x === k) ? v : undefined, "\t")} \`\`\``;
}
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;
}
_isCached(url) {
return this.cachedLinks.indexOf(normaliseUrlForCache(url)) > -1;
}
_doFetchRSS(guild) {
const feedPromise = readFeed(this.url).then(parsed => this._processLatestArticle(guild, parsed.feed.entries));
_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]));
feedPromise.catch(err => DiscordUtil.dateDebugError([`Error reading feed ${this.url}`, err]));
return feedPromise;
}
return feedPromise;
}
_processLatestArticle(guild, entries) {
if (entries.length === 0 || !entries[0].link)
return false;
_processLatestArticle(guild, entries) {
if (entries.length === 0 || !entries[0].link)
return false;
if (this._isCached(entries[0].link))
return false;
if (this._isCached(entries[0].link))
return false;
this.cache(entries[0].link);
this.cache(entries[0].link);
const channel = guild.channels.get(this.channelID),
role = guild.roles.get(this.roleID);
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}`));
channel.send((role || "") + formatPost(entries[0]))
.catch(err => DiscordUtil.dateDebugError(`Error posting in ${channel.id}: ${err.message || err}`));
return true;
}
return true;
}
};
function formatPost(article) {
let message = "";
let message = "";
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.link) message += `\n\n${normaliseUrlForDiscord(article.link)}`;
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.link) message += `\n\n${normaliseUrlForDiscord(article.link)}`;
return message;
return message;
}
function normaliseUrlForDiscord(url) {
const parsedUrl = Url.parse(url);
if (parsedUrl.host && parsedUrl.host.includes("youtube.com"))
url = normaliseYouTubeUrl(url, parsedUrl);
const parsedUrl = Url.parse(url);
if (parsedUrl.host && parsedUrl.host.includes("youtube.com"))
url = normaliseYouTubeUrl(url, parsedUrl);
return url;
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}`
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.)?)/, "");
function normaliseUrlForCache(url) {
return normaliseUrlForDiscord(url).replace(/^((https?:\/\/)?(www.)?)/, "");
}

View File

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

View File

@ -4,27 +4,27 @@ 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
name: "help",
description: "Display available commands with descriptions",
syntax: "help",
admin: false,
invoke
});
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) {
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 => {
embed.addField(command.name, `${command.description}\n**Usage:** *${name} ${command.syntax}*${userIsAdmin && command.admin ? "\n***Admin only***" : ""}`);
});
commandsArr.forEach(command => {
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");
module.exports = new Command({
name: "version",
description: "Return version number",
syntax: "version",
admin: false,
invoke
name: "version",
description: "Return version number",
syntax: "version",
admin: false,
invoke
});
function invoke() {
return Promise.resolve(ParentPackageJson.version);
return Promise.resolve(ParentPackageJson.version);
}