diff --git a/README.md b/README.md index ec339b3..de1f8da 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mercury -Simple & Customisable RSS Parser for IRC. +Simple & Customisable RSS Client for IRC. This bot is not completed, expect bugs/crashes/errors. Use in production is disadvised at this stage. @@ -8,8 +8,9 @@ This bot is not completed, expect bugs/crashes/errors. Use in production is disa ## Commands -- `m!feed [FEED] [ENTRIES]` - Return the last x amount of entries from any RSS feed. +- `m!feed [USER/FEED] [ENTRIES]` - Return the last x amount of entries from any RSS feed or your own saved feeds (if you have saved feeds) - `m!twitter [USER] [ENTRIES]` - Return the last x amount of tweets from a particular user. +- `m!set [CATEGORY] [OPTION] [VALUE]` - Control bot settings, see wiki for info on usage. ## Deployment diff --git a/bot.js b/bot.js index 5961181..3495c2c 100644 --- a/bot.js +++ b/bot.js @@ -22,6 +22,16 @@ var bot = new irc.Client(config.irc.server, config.irc.nickname, { const timer = ms => new Promise(res => setTimeout(res, ms)) +const isValidUrl = urlString=> { + var urlPattern = new RegExp('^(https?:\\/\\/)?'+ // validate protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // validate domain name + '((\\d{1,3}\\.){3}\\d{1,3}))'+ // validate OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // validate port and path + '(\\?[;&a-z\\d%_.~+=-]*)?'+ // validate query string + '(\\#[-a-z\\d_]*)?$','i'); // validate fragment locator + return !!urlPattern.test(urlString); +} + async function help(chan, sub) { if (sub === undefined) { var sub = "default" @@ -32,9 +42,10 @@ async function help(chan, sub) { bot.say(chan, ' / / / / / / __/ / / /__/ /_/ / / / /_/ / ') bot.say(chan, '/_/ /_/ /_/\\___/_/ \\___/\\__,_/_/ \\__, / ') bot.say(chan, ' /____/ ') - bot.say(chan, 'Mercury - https://git.supernets.org/hogwart7/mercury') - bot.say(chan, 'm!feed [FEED] [ENTRIES] - Return the last x amount of entries from any RSS feed') + bot.say(chan, 'Mercury RSS Client - https://git.supernets.org/hogwart7/mercury') + bot.say(chan, 'm!feed [USER/FEED] [ENTRIES] - Return the last x amount of entries from any RSS feed or your own saved feeds (if you have saved feeds)') bot.say(chan, "m!twitter [USER] [ENTRIES] - Return the last x amount of tweets from a particular user") + bot.say(chan, "m!set [CATEGORY] [OPTION] [VALUE] - Control bot settings, see wiki for info on usage.") } } @@ -55,29 +66,45 @@ async function opt(chan, user, setting, setting2, value) { } }); worker.once('message', (string) => { - console.log('Received output from last5 worker, posting.'); + console.log('Received output from options worker, posting.'); bot.say(chan, string); }); } -async function feed(chan, provfeed, n) { +async function feed(chan, nick, provfeed, n) { if (provfeed === undefined) { bot.say(chan, errorMsg+" No feed has been provided.") return; + } else if (provfeed === 'me' ) { + var provfeed = nick; } if (n === undefined) { var n = config.feed.default_amount; } - const worker = new Worker('./commands/feed.js', { - workerData: { - provfeed, - n - } - }); - worker.once('message', (string) => { - console.log('Received output from last5 worker, posting.'); - bot.say(chan, string); - }); + if (isValidUrl(provfeed) === false) { + const worker = new Worker('./commands/feed-list.js', { + workerData: { + provfeed, + n, + nick + } + }); + worker.once('message', (string) => { + console.log('Received output from feed-list worker, posting.'); + bot.say(chan, string); + }); + } else { + const worker = new Worker('./commands/feed-preset.js', { + workerData: { + provfeed, + n + } + }); + worker.once('message', (string) => { + console.log('Received output from feed-preset worker, posting.'); + bot.say(chan, string); + }); + } } async function twitter(chan, provfeed, n) { @@ -105,7 +132,7 @@ bot.addListener('message', function(nick, to, text, from) { if (args[0] === config.irc.prefix+'help') { help(to, args[1]); } else if (args[0] === config.irc.prefix+'feed') { - feed(to, args[1], args[2]); + feed(to, nick, args[1], args[2]); } else if (args[0] === config.irc.prefix+'twitter') { twitter(to, args[1], args[2]) } else if (args[0] === config.irc.prefix+'set') { diff --git a/commands/feed-list.js b/commands/feed-list.js new file mode 100644 index 0000000..dd0630d --- /dev/null +++ b/commands/feed-list.js @@ -0,0 +1,120 @@ +const config = require('../config/default.json') +const uconfig = require('../config/usersettings.json') +const { parentPort, workerData } = require('worker_threads'); +const { provfeed, n, nick } = workerData; +let Parser = require('rss-parser'); +let parser = new Parser({ + headers: {'User-Agent': config.feed.useragent}, +}); +const striptags = require("striptags"); +const moment = require('moment'); +const tz = require('moment-timezone'); +const timer = ms => new Promise(res => setTimeout(res, ms)) +const editJsonFile = require("edit-json-file"); +const { isNumber } = require('util'); + +warningMsg = ''+config.colours.brackets+'['+config.colours.warning+'WARNING'+config.colours.brackets+']' +errorMsg = ''+config.colours.brackets+'['+config.colours.error+'ERROR'+config.colours.brackets+']' + +async function sendUpstream(content) { + var output = content.join("\n") + parentPort.postMessage(output); + process.exit() +} + +function errorMessage(error, code, extra) { + console.log(error.code) + if (code == "404") { + var error = errorMsg+" 404: " + extra + " not found" + } else if (error.code == "ECONNREFUSED") { + var error = errorMsg+" Connection Refused ("+extra+")" + } else if (error.code == "ERR_UNESCAPED_CHARACTERS"){ + var error = errorMsg+" Unescaped Characters" + } else if (error == "NOFEEDS") { + var error = errorMsg+" No saved feeds for "+provfeed + } else { + var error = errorMsg+" Unknown error" + } + + parentPort.postMessage(error); + process.exit() +} + +async function fetchFeed(feedURL, n, nick) { + var content = []; + try { + var feedsArr = uconfig[nick].feeds + } catch (e) { + errorMessage(e, "NOFEEDS", nick); + } + var n = n/feedsArr.length + for (let i = 0; i < feedsArr.length; i++) { + try { + var newFeed = await parser.parseURL(feedsArr[i]); + } catch (e) { + if (e.code !== undefined) { + errorMessage(e, feedsArr[i]) + } else { + errorMessage(e, "404", feedsArr[i]); + } + } + if (n > newFeed.items.length) { + var n = newFeed.items.length; + content.push(warningMsg+" Your requested post amount exceeded the total available. Reverting to " + newFeed.items.length); + } else if (n < 1) { + var n = config.feed.default_amount + content.push(warningMsg+" You requested a number less than 1. Reverting to default ("+config.feed.default_amount+")"); + } + for (let i = 0; i < n; i++) { + var data = newFeed.items[i] + var title = data.title.replace(/(\r\n|\n|\r)/gm, " ") //remove line breaks + .replace(/\s{2,}/g, ' ') //idk + var title = striptags(title); + var body = data.contentSnippet.replace(/(\r\n|\n|\r)/gm, " ") //remove line breaks + .replace(/\s{2,}/g, ' ') //idk + var body = striptags(body); + if (data.isoDate !== undefined) { + var date = moment(data.isoDate) + var syncDate = date.tz(config.feed.timezone) + console.log(syncDate.format()) + var date = syncDate.format(config.feed.time_format) + } else { + var date = data.pubDate + } + if (body.length >= config.feed.body_max_chars) { + var truncatedString = body.substring(0,config.feed.body_max_chars); + var body = truncatedString + "..." + } + date = ''+config.colours.brackets+'['+config.colours.date+date+''+config.colours.brackets+'] ' + title = ''+config.colours.title+title+' ' + author = ''+config.colours.author+data.creator+' ' + body = ''+config.colours.body+body+' ' + link = ''+config.colours.link+data.link+' ' + //console.log(data); + //var string = "15[11" + date + "15] 08" + title + " " + body + " " + data.link; + var string = date+title+body+link; + content.push(string) + console.log(content) + } + //sendUpstream(content); + } + sendUpstream(content); + + +} + + +//var file = editJsonFile('/home/node/app/config/usersettings.json'); +//async function getAllFeeds(nick, n) { +// console.log(nick) +// var feedsArr = uconfig[nick].feeds +// console.log(feedsArr) +// var num = n/feedsArr.length +// for (let i = 0; i < feedsArr.length; i++) { +// fetchFeed(feedsArr[i], num); +// } +// await sendUpstream(content); +//} + +//getAllFeeds(nick, n) +fetchFeed(provfeed, n, provfeed); diff --git a/commands/feed.js b/commands/feed-preset.js similarity index 100% rename from commands/feed.js rename to commands/feed-preset.js diff --git a/commands/options.js b/commands/options.js index 4159d17..0ea22de 100644 --- a/commands/options.js +++ b/commands/options.js @@ -1,4 +1,5 @@ const config = require('../config/default.json') +const uconfig = require('../config/usersettings.json') const { parentPort, workerData } = require('worker_threads'); const { user, setting, setting2, value } = workerData; const fs = require('fs-extra') @@ -6,18 +7,13 @@ let Parser = require('rss-parser'); let parser = new Parser({ headers: {'User-Agent': config.feed.useragent}, }); -const striptags = require("striptags"); -const moment = require('moment'); -const tz = require('moment-timezone'); const editJsonFile = require("edit-json-file"); const timer = ms => new Promise(res => setTimeout(res, ms)) warningMsg = ''+config.colours.brackets+'['+config.colours.warning+'WARNING'+config.colours.brackets+']' errorMsg = ''+config.colours.brackets+'['+config.colours.error+'ERROR'+config.colours.brackets+']' - async function sendUpstream(content) { - //var output = content.join("\n") parentPort.postMessage(content); process.exit() } @@ -32,7 +28,9 @@ function errorMessage(error, code, extra) { var error = errorMsg+" Unescaped Characters" } else if (code == "INVALID") { var error = errorMsg+' '+extra+' either does not exist or is not a valid feed.' - } else { + } else if (code == "ALREADYEXISTS" ) { + var error = errorMsg+' '+extra+' already exists in your feed list.' + } else { var error = errorMsg+" Unknown error" } parentPort.postMessage(error); @@ -53,20 +51,17 @@ async function feed(nick, setting, value) { if (setting === 'add') { await testFeed(value); var file = editJsonFile('/home/node/app/config/usersettings.json'); - file.append(nick+".feeds", value); - file.save(); - sendUpstream(value + ' added to your feed list') - //file.custom.feed.nick = value; - //fs.writeFile(file, JSON.stringify({ feed: value })) - + var feedsArr = uconfig[nick].feeds + if (feedsArr.includes(value) == true) { + errorMessage("null", "ALREADYEXISTS", value) + } else { + file.append(nick+".feeds", value); + file.save(); + sendUpstream(value + ' added to your feed list') + } } } - -//fs.readFile('../config/feeds.json').catch(() => -// fs.writeFile('../config/feeds.json', '') -//); - if (setting === 'feed') { feed(user, setting2, value); }