2016-12-03 03:04:38 +02:00
//external library imports
2016-11-01 01:28:17 +02:00
var Dns = require ( "dns" ) ; //for connectivity checking
var Url = require ( "url" ) ; //for url parsing
var Uri = require ( "urijs" ) ; //for finding urls within message strings
var Discord = require ( "discord.io" ) ; //for obvious reasons
var FeedRead = require ( "feed-read" ) ; //for rss feed reading
2017-01-08 09:09:28 +02:00
var JsonFile = require ( "jsonfile" ) ; //reading/writing json
2016-12-03 03:04:38 +02:00
//my imports
var Log = require ( "./log.js" ) ; //some very simple logging functions I made
2016-12-02 02:57:37 +02:00
var BotConfig = require ( "./bot-config.json" ) ; //bot config file containing bot token
2016-11-01 01:28:17 +02:00
var Config = require ( "./config.json" ) ; //config file containing other settings
2016-11-01 19:23:39 +02:00
2016-12-30 16:47:56 +02:00
var DiscordClient = {
2016-12-03 02:56:31 +02:00
bot : null ,
startup : function ( ) {
//check if we can connect to discordapp.com to authenticate the bot
Dns . resolve ( "discordapp.com" , function ( err ) {
2017-01-04 21:53:47 +02:00
if ( err )
Log . error ( "CONNECTION ERROR: Unable to locate discordapp.com to authenticate the bot" , err ) ;
2016-12-03 02:56:31 +02:00
else {
//if there was no error, go ahead and create and authenticate the bot
2016-12-30 16:47:56 +02:00
DiscordClient . bot = new Discord . Client ( {
2016-12-03 02:56:31 +02:00
token : BotConfig . token ,
autorun : true
} ) ;
2016-10-29 20:31:16 +03:00
2016-12-03 02:56:31 +02:00
//set up the bot's event handlers
2016-12-30 16:47:56 +02:00
DiscordClient . bot . on ( "ready" , DiscordClient . onReady ) ;
DiscordClient . bot . on ( "disconnect" , DiscordClient . onDisconnect ) ;
DiscordClient . bot . on ( "message" , DiscordClient . onMessage ) ;
2016-12-03 02:56:31 +02:00
}
} ) ;
} ,
onReady : function ( ) {
2016-12-30 16:51:25 +02:00
Log . info ( "Registered/connected bot " + DiscordClient . bot . username + " - (" + DiscordClient . bot . id + ")" ) ;
2016-12-03 02:56:31 +02:00
2017-01-04 21:53:47 +02:00
DiscordClient . checkPastMessagesForLinks ( ) ; //we need to check past messages for links on startup, but also on reconnect because we don't know what has happened during the downtime
2017-01-08 07:00:07 +02:00
2017-01-10 00:55:05 +02:00
//set the interval function to check the feed
2017-01-08 07:24:48 +02:00
intervalFunc = ( ) => {
2017-01-08 07:41:10 +02:00
Feed . check ( ( err , articles ) => {
2017-01-08 07:24:48 +02:00
Links . validate ( err , articles , DiscordClient . post ) ;
2017-01-08 07:41:10 +02:00
} ) ;
2017-01-08 07:24:48 +02:00
} ;
2016-12-03 02:56:31 +02:00
} ,
onDisconnect : function ( err , code ) {
2017-01-04 21:53:47 +02:00
Log . event ( "Bot was disconnected! " + ( err ? err : "" ) + ( code ? code : "No disconnect code provided." ) + "\nClearing the feed timer and starting reconnect timer" , "Discord.io" ) ;
2016-12-03 02:56:31 +02:00
2017-01-08 07:00:07 +02:00
intervalFunc = DiscordClient . startup ; //reassign the interval function to try restart the bot every 5 sec
2016-12-03 02:56:31 +02:00
} ,
onMessage : function ( user , userID , channelID , message ) {
2017-01-08 09:45:22 +02:00
if ( channelID === Config . channelID ) {
//contains a link, and is not the latest link from the rss feed
if ( Links . messageContainsLink ( message ) && ( message !== Links . latestFromFeedlatestFeedLink ) ) {
Log . event ( "Detected posted link in this message: " + message , "Discord.io" ) ;
//extract the url from the string, and cache it
Uri . withinString ( message , function ( url ) {
Links . cache ( Links . standardise ( url ) ) ;
return url ;
} ) ;
}
2017-01-22 22:25:35 +02:00
}
else {
//iterate over all of our message triggers to see if the message sent requires any action
for ( var i = 0 ; i < DiscordClient . messageTriggers . length ; i ++ ) {
var messageTrigger = DiscordClient . messageTriggers [ i ] ;
if ( message === messageTrigger . message ) {
//check if its locked to a channel or to a specific user
if ( ( messageTrigger . channelID && messageTrigger . channelID === channelID ) || ( messageTrigger . userIDs && messageTrigger . userIDs . includes ( userID ) ) )
messageTrigger . action ( user , userID , channelID , message ) ;
2017-01-08 18:01:07 +02:00
}
2017-01-08 09:45:22 +02:00
}
2017-01-08 08:44:08 +02:00
}
2017-01-08 07:27:49 +02:00
} ,
2016-12-03 02:56:31 +02:00
checkPastMessagesForLinks : function ( ) {
var limit = 100 ;
Log . info ( "Attempting to check past " + limit + " messages for links" ) ;
//get the last however many messsages from our discord channel
2016-12-30 16:47:56 +02:00
DiscordClient . bot . getMessages ( {
2016-12-03 02:56:31 +02:00
channelID : Config . channelID ,
limit : limit
} , function ( err , messages ) {
if ( err ) Log . error ( "Error fetching discord messages." , err ) ;
else {
Log . info ( "Pulled last " + messages . length + " messages, scanning for links" ) ;
2016-12-30 17:37:13 +02:00
var messageContents = messages . map ( ( x ) => { return x . content ; } ) . reverse ( ) ; //extract an array of strings from the array of message objects
2016-12-03 02:56:31 +02:00
for ( var messageIdx in messageContents ) {
var message = messageContents [ messageIdx ] ;
2016-12-30 17:37:13 +02:00
if ( Links . messageContainsLink ( message ) ) //test if the message contains a url
2016-12-03 02:56:31 +02:00
//detect the url inside the string, and cache it
Uri . withinString ( message , function ( url ) {
Links . cache ( url ) ;
return url ;
} ) ;
}
}
} ) ;
2017-01-08 07:24:48 +02:00
} ,
post : function ( link ) {
//send a messsage containing the new feed link to our discord channel
DiscordClient . bot . sendMessage ( {
to : Config . channelID ,
2017-01-22 19:11:12 +02:00
message : Subscriptions . mention ( ) + link
2017-01-08 07:24:48 +02:00
} ) ;
2017-01-09 23:57:32 +02:00
} ,
//actions to perform when certain messages are detected, along with channel or user requirements
messageTriggers : [
{
message : Config . userCommands . subscribe ,
2017-01-10 01:08:52 +02:00
action : ( user , userID , channelID , message ) => { if ( Config . allowSubscriptions ) Subscriptions . subscribe ( user , userID , channelID , message ) ; } ,
2017-01-09 23:57:32 +02:00
channelID : Config . channelID
} ,
{
message : Config . userCommands . unsubscribe ,
2017-01-10 01:08:52 +02:00
action : ( user , userID , channelID , message ) => { if ( Config . allowSubscriptions ) Subscriptions . unsubscribe ( user , userID , channelID , message ) ; } ,
2017-01-09 23:57:32 +02:00
channelID : Config . channelID
} ,
{
message : Config . userCommands . help ,
action : ( user , userID , channelID , message ) => {
DiscordClient . bot . sendMessage ( {
to : Config . channelID ,
message : Config . userCommands . join ( " + " )
} ) ;
} ,
channelID : Config . channelID
} ,
{
message : Config . developerCommands . logUpload ,
action : ( user , userID , channelID , message ) => {
DiscordClient . bot . uploadFile ( {
to : channelID ,
file : Config . logFile
} ) ;
} ,
userIDs : Config . developers
} ,
{
message : Config . developerCommands . cacheList ,
action : ( user , userID , channelID , message ) => {
DiscordClient . bot . sendMessage ( {
to : channelID ,
message : Links . cached . join ( ", " )
} ) ;
} ,
userIDs : Config . developers
}
]
2016-12-03 02:56:31 +02:00
} ;
2016-11-01 01:28:17 +02:00
2017-01-08 09:45:22 +02:00
var Subscriptions = {
2017-01-10 00:55:05 +02:00
subscribe : function ( user , userID , channelID , message ) {
2017-01-22 19:11:12 +02:00
DiscordClient . bot . addToRole ( {
2017-01-22 20:08:40 +02:00
serverID : Config . serverID ,
2017-01-22 19:11:12 +02:00
userID : userID ,
roleID : Config . subscribersRoleID
2017-01-22 20:23:09 +02:00
} ,
( err ) => {
if ( err ) Log . raw ( err ) ; //log the error if there is an error
else { //else go ahead and confirm subscription
Log . event ( "Subscribed user " + ( user ? user + "(" + userID + ")" : userID ) ) ;
DiscordClient . bot . sendMessage ( {
to : channelID ,
message : "You have successfully subscribed"
} , ( err , response ) => { setTimeout ( ( ) => { DiscordClient . bot . deleteMessage ( { channelID : channelID , messageID : response . id } ) ; } , Config . messageDeleteDelay ) ; } ) ; //delete the subscription confirmation message after a delay
}
} ) ;
2017-01-10 01:23:14 +02:00
2017-01-08 09:45:22 +02:00
} ,
2017-01-22 19:11:12 +02:00
2017-01-10 00:55:05 +02:00
unsubscribe : function ( user , userID , channelID , message ) {
2017-01-22 19:11:12 +02:00
DiscordClient . bot . removeFromRole ( {
2017-01-22 20:08:40 +02:00
serverID : Config . serverID ,
2017-01-22 19:11:12 +02:00
userID : userID ,
roleID : Config . subscribersRoleID
2017-01-22 20:23:09 +02:00
} ,
( err ) => {
if ( err ) Log . raw ( err ) ; //log the error if there is an error
else { //else go ahead and confirm un-subscription
Log . event ( "Unsubscribed user " + ( user ? user + "(" + userID + ")" : userID ) ) ;
DiscordClient . bot . sendMessage ( {
to : channelID ,
message : "You have successfully unsubscribed"
} , ( err , response ) => { setTimeout ( ( ) => { DiscordClient . bot . deleteMessage ( { channelID : channelID , messageID : response . id } ) ; } , Config . messageDeleteDelay ) ; } ) ; //delete the un-subscription confirmation message after a delay
}
} ) ;
2017-01-08 18:01:07 +02:00
} ,
2017-01-22 19:11:12 +02:00
mention : function ( ) {
2017-01-22 20:24:18 +02:00
return Config . allowSubscriptions ? "<@&" + Config . subscribersRoleID + "> " : "" ;
2017-01-08 09:45:22 +02:00
}
} ;
2016-12-03 02:23:15 +02:00
var YouTube = {
url : {
share : "http://youtu.be/" ,
full : "http://www.youtube.com/watch?v=" ,
2017-01-08 07:24:48 +02:00
createFullUrl : function ( shareUrl ) {
2016-12-03 03:52:16 +02:00
return shareUrl . replace ( YouTube . url . share , YouTube . url . full ) ;
2016-12-03 02:23:15 +02:00
} ,
2017-01-08 07:24:48 +02:00
createShareUrl : function ( fullUrl ) {
2016-12-03 03:52:16 +02:00
var shareUrl = fullUrl . replace ( YouTube . url . full , YouTube . url . share ) ;
2016-12-02 23:53:13 +02:00
2017-01-08 07:24:48 +02:00
if ( shareUrl . includes ( "&" ) ) shareUrl = shareUrl . slice ( 0 , fullUrl . indexOf ( "&" ) ) ;
2016-12-03 02:23:15 +02:00
return shareUrl ;
}
} ,
} ;
var Links = {
2016-12-30 16:54:10 +02:00
standardise : function ( link ) {
2016-12-30 17:40:03 +02:00
link = link . replace ( "https://" , "http://" ) ; //cheaty way to get around http and https not matching
2017-01-04 21:06:29 +02:00
if ( Config . youtubeMode ) link = link . split ( "&" ) [ 0 ] ; //quick way to chop off stuff like &feature=youtube etc
2016-12-30 17:40:03 +02:00
return link ;
2016-12-30 16:54:10 +02:00
} ,
messageContainsLink : function ( message ) {
var messageLower = message . toLowerCase ( ) ;
2016-12-30 17:24:12 +02:00
return messageLower . includes ( "http://" ) || messageLower . includes ( "https://" ) || messageLower . includes ( "www." ) ;
2016-12-30 16:54:10 +02:00
} ,
2016-12-03 02:23:15 +02:00
cached : [ ] ,
2017-01-08 07:24:48 +02:00
latestFeedLink : "" ,
2016-12-03 02:23:15 +02:00
cache : function ( link ) {
2016-12-30 16:54:10 +02:00
link = Links . standardise ( link ) ;
2016-12-03 03:52:16 +02:00
2017-01-08 07:24:48 +02:00
if ( Config . youtubeMode ) link = YouTube . url . createShareUrl ( link ) ;
2016-12-03 03:52:16 +02:00
2016-12-03 02:23:15 +02:00
//store the new link if not stored already
2017-01-08 07:24:48 +02:00
if ( ! Links . isCached ( link ) ) {
2016-12-03 03:37:02 +02:00
Links . cached . push ( link ) ;
2016-12-03 02:23:15 +02:00
Log . info ( "Cached URL: " + link ) ;
}
2017-01-08 07:24:48 +02:00
2017-01-08 07:27:49 +02:00
if ( Links . cached . length > Config . numLinksToCache ) Links . cached . shift ( ) ; //get rid of the first array element if we have reached our cache limit
2016-12-03 02:23:15 +02:00
} ,
2017-01-08 07:24:48 +02:00
isCached : function ( link ) {
2016-12-30 16:54:10 +02:00
link = Links . standardise ( link ) ;
2017-01-08 07:24:48 +02:00
if ( Config . youtubeMode )
return Links . cached . includes ( YouTube . url . createShareUrl ( link ) ) ;
2016-12-03 03:37:02 +02:00
return Links . cached . includes ( link ) ;
2016-12-03 02:56:31 +02:00
} ,
2017-01-08 07:24:48 +02:00
validate : function ( err , articles , callback ) {
2016-12-03 02:56:31 +02:00
if ( err ) Log . error ( "FEED ERROR: Error reading RSS feed." , err ) ;
else {
2017-01-08 08:18:05 +02:00
var latestLink = Links . standardise ( articles [ 0 ] . link ) ;
2017-01-08 08:37:30 +02:00
if ( Config . youtubeMode ) latestLink = YouTube . url . createShareUrl ( latestLink ) ;
2016-12-03 02:56:31 +02:00
2017-01-08 07:24:48 +02:00
//make sure we don't spam the latest link
2017-01-08 10:48:47 +02:00
if ( latestLink === Links . latestFeedLink )
2017-01-08 07:24:48 +02:00
return ;
2016-12-02 02:55:39 +02:00
2017-01-08 07:24:48 +02:00
//make sure the latest link hasn't been posted already
if ( Links . isCached ( latestLink ) ) {
Log . info ( "Didn't post new feed link because already detected as posted " + latestLink ) ;
2016-12-02 02:40:33 +02:00
}
2017-01-08 08:37:30 +02:00
else {
callback ( latestLink ) ;
2016-12-02 02:55:39 +02:00
2017-01-08 08:37:30 +02:00
Links . cache ( latestLink ) ; //make sure the link is cached, so it doesn't get posted again
}
2017-01-08 08:44:08 +02:00
2017-01-08 07:24:48 +02:00
Links . latestFeedLink = latestLink ; //ensure our latest feed link variable is up to date, so we can track when the feed updates
2016-11-01 01:28:17 +02:00
}
}
2016-12-03 02:56:31 +02:00
} ;
2016-11-05 03:08:17 +02:00
2016-12-03 02:56:31 +02:00
var Feed = {
urlObj : Url . parse ( Config . feedUrl ) ,
2017-01-08 07:24:48 +02:00
check : function ( callback ) {
2016-12-30 17:37:13 +02:00
Dns . resolve ( Feed . urlObj . host , function ( err ) { //check that we have an internet connection (well not exactly - check that we have a connection to the host of the feedUrl)
2016-12-30 16:54:10 +02:00
if ( err ) Log . error ( "CONNECTION ERROR: Cannot resolve host." , err ) ;
2017-01-08 07:41:10 +02:00
else FeedRead ( Config . feedUrl , callback ) ;
2016-12-03 02:56:31 +02:00
} ) ;
}
2016-12-03 03:37:02 +02:00
} ;
2017-01-08 07:24:48 +02:00
var intervalFunc = ( ) => { } ; //do nothing by default
2017-01-08 07:00:07 +02:00
2016-12-03 03:37:02 +02:00
//IIFE to kickstart the bot when the app loads
2016-12-30 16:54:10 +02:00
( function ( ) {
2016-12-30 16:47:56 +02:00
DiscordClient . startup ( ) ;
2017-01-08 07:41:10 +02:00
setInterval ( ( ) => { intervalFunc ( ) ; } , Config . pollingInterval ) ;
2016-12-03 03:37:02 +02:00
} ) ( ) ;