diff --git a/README.md b/README.md index fc6b559..8599b1f 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ With this Glitch starter project, you can create a bot that anyone [in the fediverse](https://en.wikipedia.org/wiki/Fediverse) can follow. This project is [in early development](https://github.com/botwiki/glitch-fediverse-bot/issues) with more features coming. -To automate your bot, set up a free service like [cron-job.org](https://cron-job.org/en/), [Uptime Robot](https://uptimerobot.com/), or [a similar one](https://www.google.com/search?q=free+web+cron) to wake up your bot [every 25+ minutes](https://support.glitch.com/t/a-simple-twitter-bot-template/747/16). Use `https://YOUR_PROJECT_NAME.glitch.me/BOT_ENDPOINT` as a URL to which to send the HTTP request. (`BOT_ENDPOINT` is set in your `.env` file.) - ## Bot administration @@ -17,7 +15,7 @@ You can log into the admin panel by going to `YOUR_PROJECT_NAME.glitch.me/admin` ## Bot logic (the back end) -Your bot's logic is inside the `routes/bot-endpoint.js` file. This is the code that runs when you access your bot's endpoint, as defined inside the `.env` file. See the `examples` folder for some examples of what your bot can do. +*TBD* ## The look of your bot's page (the front end) diff --git a/app.js b/app.js index e2307e4..7bf4c53 100644 --- a/app.js +++ b/app.js @@ -1,78 +1,78 @@ -var path = require('path'), - express = require('express'), - session = require('express-session'), - SQLiteStore = require('connect-sqlite3')(session), - exphbs = require('express-handlebars'), - bodyParser = require('body-parser'), - pubSubHubbub = require('pubsubhubbub'), - sassMiddleware = require('node-sass-middleware'), - babelify = require('express-babelify-middleware'), - helpers = require(__dirname + '/helpers/general.js'), - db = require(__dirname + '/helpers/db.js'), - app = express(); +const path = require( 'path' ), + express = require( 'express' ), + session = require( 'express-session' ), + SQLiteStore = require( 'connect-sqlite3' )( session ), + exphbs = require( 'express-handlebars' ), + bodyParser = require( 'body-parser' ), + pubSubHubbub = require( 'pubsubhubbub' ), + sassMiddleware = require( 'node-sass-middleware' ), + babelify = require( 'express-babelify-middleware' ), + helpers = require( __dirname + '/helpers/general.js' ), + db = require( __dirname + '/helpers/db.js' ), + app = express(); -app.use(express.static('public')); +app.use( express.static( 'public' ) ); -app.use(bodyParser.json({ +app.use( bodyParser.json( { type: 'application/activity+json' -})); +} ) ); -app.use(bodyParser.urlencoded({ +app.use( bodyParser.urlencoded( { extended: true -})); +} ) ); -app.use(session({ +app.use( session( { store: new SQLiteStore, secret: process.env.ADMIN_PASSWORD, resave: true, saveUninitialized: true, cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 } -})); +} ) ); -app.use(sassMiddleware({ +app.use( sassMiddleware( { // src: __dirname, src: __dirname + '/src/styles', - dest: path.join(__dirname, 'public'), + dest: path.join( __dirname, 'public' ), force: true, // debug: true, outputStyle: 'compressed', response: true -})); +} ) ); -app.use('/js/scripts.js', babelify('src/scripts/scripts.js', { +app.use( '/js/scripts.js', babelify( 'src/scripts/scripts.js', { minify: true -})); +} ) ); -app.engine('handlebars', exphbs({ +app.use('/node_modules', express.static(__dirname + '/node_modules/')); + +app.engine( 'handlebars', exphbs( { defaultLayout: 'main', helpers: { - for: require('./handlebars-helpers/for'), - equals: require('./handlebars-helpers/equals') + for: require( './handlebars-helpers/for' ), + equals: require( './handlebars-helpers/equals' ) } -})); - -app.set('views', __dirname + '/views'); -app.set('view engine', 'handlebars'); +} ) ); -app.use('/', require('./routes/index.js')) -app.use('/admin', require('./routes/admin.js')); -app.use('/bot', require('./routes/bot.js')); -app.use('/delete-post', require('./routes/delete-post.js')); -app.use('/feed', require('./routes/feed.js')); -app.use('/img', express.static(__dirname + '/.data/img/')); +app.set( 'views', __dirname + '/views' ); +app.set( 'view engine', 'handlebars' ); -app.use('/inbox', require('./routes/inbox.js')); -app.use('/outbox', require('./routes/outbox.js')); -app.use('/post', require('./routes/post.js')); -app.use('/pubsub', require('./routes/pubsub.js')); -app.use('/salmon', require('./routes/salmon.js')); -app.use('/webhook', require('./routes/webhook.js')); -app.use('/.well-known', require('./routes/well-known.js')); +app.use( '/', require( './routes/index.js' ) ) +app.use( '/admin', require( './routes/admin.js' ) ); +app.use( '/bot', require( './routes/bot.js' ) ); +app.use( '/delete-post', require( './routes/delete-post.js' ) ); +app.use( '/feed', require( './routes/feed.js' ) ); +app.use( '/img', express.static( __dirname + '/.data/img/' ) ); -app.use(`/${process.env.BOT_ENDPOINT}`, require('./routes/bot-endpoint.js')); +app.use( '/inbox', require( './routes/inbox.js' ) ); +app.use( '/outbox', require( './routes/outbox.js' ) ); +app.use( '/post', require( './routes/post.js' ) ); +app.use( '/pubsub', require( './routes/pubsub.js' ) ); +app.use( '/salmon', require( './routes/salmon.js' ) ); +app.use( '/webhook', require( './routes/webhook.js' ) ); +app.use( '/.well-known', require( './routes/well-known.js' ) ); -app.get('/js/helpers.js', function (req, res) { - res.sendFile(path.join(__dirname + '/helpers/general.js')); -}); +app.get( '/js/helpers.js', function ( req, res ) { + res.sendFile( path.join( __dirname + '/helpers/general.js' ) ); +} ); module.exports = app; diff --git a/bot/bot.js b/bot/bot.js index 1e4be87..4d70ec2 100644 --- a/bot/bot.js +++ b/bot/bot.js @@ -1,54 +1,54 @@ -var fs = require('fs'), - crypto = require('crypto'), - url = require('url'), - util = require('util'), - moment = require('moment'), - dbHelper = require(__dirname + '/../helpers/db.js'), - keys = require(__dirname + '/../helpers/keys.js'), - request = require('request'), - public_key_path = '.data/rsa/pubKey', - private_key_path = '.data/rsa/privKey', - bot_url = `https://${process.env.PROJECT_DOMAIN}.glitch.me`, - botComposeReply = require(__dirname + '/responses.js'); +const fs = require( 'fs' ), + crypto = require( 'crypto' ), + url = require( 'url' ), + util = require( 'util' ), + moment = require( 'moment' ), + dbHelper = require( __dirname + '/../helpers/db.js' ), + keys = require( __dirname + '/../helpers/keys.js' ), + request = require( 'request' ), + publicKeyPath = '.data/rsa/pubKey', + privateKeyPath = '.data/rsa/privKey', + botURL = `https://${ process.env.PROJECT_DOMAIN }.glitch.me`, + botComposeReply = require( __dirname + '/responses.js' ); -if (!fs.existsSync(public_key_path) || !fs.existsSync(private_key_path)) { - keys.generate_keys(function(){ - process.kill(process.pid); - }); +if ( !fs.existsSync( publicKeyPath ) || !fs.existsSync( privateKeyPath ) ) { + keys.generateKeys( function( ){ + process.kill( process.pid ); + } ); } else{ - var public_key = fs.readFileSync(public_key_path, 'utf8'), - private_key = fs.readFileSync(private_key_path, 'utf8'); + let publicKey = fs.readFileSync( publicKeyPath, 'utf8' ), + privateKey = fs.readFileSync( privateKeyPath, 'utf8' ); module.exports = { - bot_url: bot_url, + bot_url: botURL, links: [ // { // rel: 'http://webfinger.net/rel/profile-page', // type: 'text/html', - // href: `${bot_url}` + // href: `${ botURL }` // }, // { // rel: 'http://schemas.google.com/g/2010#updates-from', // type: 'application/atom+xml', - // href: `${bot_url}/feed` + // href: `${ botURL }/feed` // }, { rel: 'self', type: 'application/activity+json', - href: `${bot_url}/bot` + href: `${ botURL }/bot` }, // { // rel: 'hub', - // href: `${bot_url}/pubsub` + // href: `${ botURL }/pubsub` // }, // { // rel: 'salmon', - // href: `${bot_url}/salmon` + // href: `${ botURL }/salmon` // }, // { // rel: 'magic-public-key', - // href: `data:application/magic-public-key,RSA.${public_key.replace('-----BEGIN PUBLIC KEY-----\n', '').replace('\n-----END PUBLIC KEY-----', '').replace('\\n', '')}` + // href: `data:application/magic-public-key,RSA.${ publicKey.replace( '-----BEGIN PUBLIC KEY-----\n', '' ).replace( '\n-----END PUBLIC KEY-----', '' ).replace( '\\n', '' ) }` // } ], info: { @@ -56,7 +56,7 @@ else{ 'https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1' ], - 'id': `${bot_url}/bot`, + 'id': `${ botURL }/bot`, 'icon': [{ 'url': process.env.BOT_AVATAR_URL, 'type': 'Image' @@ -68,223 +68,225 @@ else{ 'type': 'Person', 'name': process.env.BOT_USERNAME, 'preferredUsername': process.env.BOT_USERNAME, - 'inbox': `${bot_url}/inbox`, + 'inbox': `${ botURL }/inbox`, 'publicKey': { - 'id': `${bot_url}/bot#main-key`, - 'owner': `${bot_url}/bot`, - 'publicKeyPem': public_key + 'id': `${ botURL }/bot#main-key`, + 'owner': `${ botURL }/bot`, + 'publicKeyPem': publicKey } }, + // script: botScript, + script: require( __dirname + '/script.js' ), composeReply: botComposeReply, - sendReply: function(options, cb){ - var bot = this, - reply_to_username = ''; + sendReply: function( options, cb ){ + let bot = this, + replyToUsername = ''; try{ - var actor_url_parts = options.payload.actor.split('/'); - var username = actor_url_parts[actor_url_parts.length-1]; - reply_to_username = `@${username}@${url.parse(options.payload.actor).hostname} `; + let actor_url_parts = options.payload.actor.split( '/' ); + let username = actor_url_parts[actor_url_parts.length-1]; + replyToUsername = `@${ username }@${ url.parse( options.payload.actor ).hostname } `; - console.log({reply_to_username}); - } catch(err){ /*noop*/ } + console.log( {replyToUsername} ); + } catch( err ){ /*noop*/ } - bot.createPost({ + bot.createPost( { type: 'Note', - content: `
${options.payload.object.content}${options.payload.object.url}
${options.reply_message}
`, - reply_message: `${reply_to_username} ${options.reply_message}`, + content: `${ options.payload.object.content }${ options.payload.object.url }
${ options.reply_message }
`, + reply_message: `${ replyToUsername } ${ options.reply_message}`, in_reply_to: options.payload.object.url - }, function(err, message){ - // console.log(err, message); - }); + }, function( err, message ){ + // console.log( err, message ); + } ); }, - createPost: function(options, cb){ - var bot = this; + createPost: function( options, cb ){ + let bot = this; - if ((!options.content || options.content.trim().length === 0 ) && !options.attachment ){ - console.log('error: no post content or attachments'); + if ( ( !options.content || options.content.trim( ).length === 0 ) && !options.attachment ){ + console.log( 'error: no post content or attachments' ); return false; } - var post_type = options.type || 'Note', + let post_type = options.type || 'Note', post_description = options.description, - post_date = moment().format(), + post_date = moment( ).format( ), post_in_reply_to = options.in_reply_to || null, reply_message = options.reply_message || null, post_content = options.content || options.url || '', - post_attachment = JSON.stringify(options.attachment) || '[]'; + post_attachment = JSON.stringify( options.attachment ) || '[]'; - dbHelper.savePost({ + dbHelper.savePost( { type: post_type, content: post_content, attachment: post_attachment - }, function(err, data){ - var post_id = data.lastID; + }, function( err, data ){ + let post_id = data.lastID; - var post_object; + let post_object; - if ( post_type === 'Note' ){ + if ( post_type === 'Note' ){ post_object = { - 'id': `${bot_url}/post/${post_id}`, + 'id': `${ botURL }/post/${ post_id }`, 'type': post_type, 'published': post_date, - 'attributedTo': `${bot_url}/bot`, + 'attributedTo': `${ botURL }/bot`, 'content': reply_message || post_content, 'to': 'https://www.w3.org/ns/activitystreams#Public' }; - if (options.attachment){ - var attachments = []; + if ( options.attachment ){ + let attachments = []; - options.attachment.forEach(function(attachment){ - attachments.push({ + options.attachment.forEach( function( attachment ){ + attachments.push( { 'type': 'Image', 'content': attachment.content, 'url': attachment.url - }); - }); + } ); + } ); post_object.attachment = attachments; } } - if (post_in_reply_to){ + if ( post_in_reply_to ){ post_object.inReplyTo = post_in_reply_to; } - var post = { + let post = { '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `${bot_url}/post/${post_id}`, + 'id': `${ botURL }/post/${ post_id }`, 'type': 'Create', - 'actor': `${bot_url}/bot`, + 'actor': `${ botURL }/bot`, 'object': post_object } - console.log({post_in_reply_to}); + console.log( {post_in_reply_to} ); - dbHelper.getFollowers(function(err, followers){ - if (followers){ - console.log(`sending update to ${followers.length} follower(s)...`); + dbHelper.getFollowers( function( err, followers ){ + if ( followers ){ + console.log( `sending update to ${ followers.length } follower( s )...` ); - followers.forEach(function(follower){ - if (follower.url){ - bot.signAndSend({ + followers.forEach( function( follower ){ + if ( follower.url ){ + bot.signAndSend( { follower: follower, message: post - }, function(err, data){ + }, function( err, data ){ - }); + } ); } - }); + } ); } - }); + } ); - if (cb){ - cb(null, post); + if ( cb ){ + cb( null, post ); } - }); + } ); }, - deletePost: function(post_id, follower_url, cb){ - var bot = this; - // guid = crypto.randomBytes(16).toString('hex'); + deletePost: function( post_id, follower_url, cb ){ + let bot = this; + // guid = crypto.randomBytes( 16 ).toString( 'hex' ); - bot.signAndSend({ + bot.signAndSend( { follower: { url: follower_url }, message: { '@context': 'https://www.w3.org/ns/activitystreams', - // 'summary': `${bot} deleted a post`, - // 'id': `${bot.bot_url}/${guid}`, + // 'summary': `${ bot} deleted a post`, + // 'id': `${ bot.bot_url}/${ guid}`, 'type': 'Delete', - 'actor': `${bot.bot_url}/bot`, - 'object': `${bot.bot_url}/post/${post_id}` + 'actor': `${ bot.bot_url }/bot`, + 'object': `${ bot.bot_url }/post/${ post_id }` } - }, function(err, data){ - if (cb){ - cb(err, data); + }, function( err, data ){ + if ( cb ){ + cb( err, data ); } - }); + } ); }, - accept: function(payload, cb){ - var bot = this, - guid = crypto.randomBytes(16).toString('hex'); + accept: function( payload, cb ){ + let bot = this, + guid = crypto.randomBytes( 16 ).toString( 'hex' ); - dbHelper.getEvent(payload.id, function(err, data){ - // console.log('get_event', err, data); + dbHelper.getEvent( payload.id, function( err, data ){ + // console.log( 'get_event', err, data ); - bot.signAndSend({ + bot.signAndSend( { follower: { url: payload.actor }, message: { '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `${bot.bot_url}/${guid}`, + 'id': `${ bot.bot_url }/${ guid }`, 'type': 'Accept', - 'actor': `${bot.bot_url}/bot`, + 'actor': `${ bot.bot_url }/bot`, 'object': payload, } - }, function(err, data){ - if (cb){ - cb(err, payload, data); + }, function( err, data ){ + if ( cb ){ + cb( err, payload, data ); } - console.log('saving event', payload.id) - dbHelper.saveEvent(payload.id); - }); + console.log( 'saving event', payload.id ) + dbHelper.saveEvent( payload.id ); + } ); - }); -// dbHelper.getEvent(payload.id, function(err, data){ -// console.log('get_event', err, data); + } ); +// dbHelper.getEvent( payload.id, function( err, data ){ +// console.log( 'get_event', err, data ); -// if (!err && !data){ -// bot.signAndSend({ +// if ( !err && !data ){ +// bot.signAndSend( { // follower: { // url: payload.actor // }, // message: { // '@context': 'https://www.w3.org/ns/activitystreams', -// 'id': `${bot.bot_url}/${guid}`, +// 'id': `${ bot.bot_url }/${ guid }`, // 'type': 'Accept', -// 'actor': `${bot.bot_url}/bot`, +// 'actor': `${ bot.bot_url }/bot`, // 'object': payload, // } -// }, function(err, data){ -// if (cb){ -// cb(err, payload, data); +// }, function( err, data ){ +// if ( cb ){ +// cb( err, payload, data ); // } -// console.log('saving event', payload.id) -// dbHelper.saveEvent(payload.id); -// }); -// } else if (!err){ -// console.log('duplicate event'); +// console.log( 'saving event', payload.id ) +// dbHelper.saveEvent( payload.id ); +// } ); +// } else if ( !err ){ +// console.log( 'duplicate event' ); // } -// }); +// } ); }, - signAndSend: function(options, cb){ - var bot = this; - // console.log('message to sign:'); - // console.log(util.inspect(options.message, false, null, true)); + signAndSend: function( options, cb ){ + let bot = this; + // console.log( 'message to sign:' ); + // console.log( util.inspect( options.message, false, null, true ) ); - options.follower.url = options.follower.url.replace('http://localhost:3000', 'https://befc66af.ngrok.io'); + options.follower.url = options.follower.url.replace( 'http://localhost:3000', 'https://befc66af.ngrok.io' ); - if (options.follower.url && options.follower.url !== 'undefined'){ - options.follower.domain = url.parse(options.follower.url).hostname; + if ( options.follower.url && options.follower.url !== 'undefined' ){ + options.follower.domain = url.parse( options.follower.url ).hostname; - var signer = crypto.createSign('sha256'), - d = new Date(), - string_to_sign = `(request-target): post /inbox\nhost: ${options.follower.domain}\ndate: ${d.toUTCString()}`; + let signer = crypto.createSign( 'sha256' ), + d = new Date( ), + string_to_sign = `( request-target ): post /inbox\nhost: ${ options.follower.domain}\ndate: ${ d.toUTCString() }`; - signer.update(string_to_sign); - signer.end(); + signer.update( string_to_sign ); + signer.end( ); - var signature = signer.sign(private_key); - var signature_b64 = signature.toString('base64'); - var header = `keyId="${bot_url}/bot",headers="(request-target) host date",signature="${signature_b64}"`; + let signature = signer.sign( privateKey ); + let signatureB64 = signature.toString( 'base64' ); + let header = `keyId="${ botURL }/bot",headers="( request-target ) host date",signature="${ signatureB64 }"`; - var req_object = { - url: `https://${options.follower.domain}/inbox`, + let reqObject = { + url: `https://${ options.follower.domain }/inbox`, headers: { 'Host': options.follower.domain, - 'Date': d.toUTCString(), + 'Date': d.toUTCString( ), 'Signature': header }, method: 'POST', @@ -292,23 +294,23 @@ else{ body: options.message }; - // console.log('request object:'); - // console.log(util.inspect(req_object, false, null, true)); + console.log( 'request object:' ); + console.log( util.inspect( reqObject, false, null, true ) ); - request(req_object, function (error, response){ - console.log(`sent message to ${options.follower.url}...`); - if (error) { - console.log('error:', error, response); + request( reqObject, function ( error, response ){ + console.log( `sent message to ${ options.follower.url }...` ); + if ( error ) { + console.log( 'error:', error, response ); } else { - console.log('response:', response.statusCode, response.statusMessage); - // console.log(response); + console.log( 'response:', response.statusCode, response.statusMessage ); + console.log( response.body ); } - if (cb){ - cb(error, response); + if ( cb ){ + cb( error, response ); } - }); + } ); } } }; diff --git a/bot/script.js b/bot/script.js new file mode 100644 index 0000000..83dd5c0 --- /dev/null +++ b/bot/script.js @@ -0,0 +1,19 @@ +const express = require( 'express' ), + router = express.Router(), + grammar = require( __dirname + '/../tracery/tracery.js' ).grammar; + +module.exports = function(){ + const bot = require( __dirname + '/bot.js' ), + content = grammar.flatten( '#origin#' ); + + console.log( 'posting new message...' ); + + bot.createPost( { + type: 'Note', // See www.w3.org/ns/activitystreams#objects + content: content + }, function( err, message ){ + + + } ); + +}; diff --git a/helpers/cron-schedules.js b/helpers/cron-schedules.js new file mode 100644 index 0000000..55b88a4 --- /dev/null +++ b/helpers/cron-schedules.js @@ -0,0 +1,20 @@ +module.exports = { + EVERY_FIVE_SECONDS: '*/5 * * * * *', + EVERY_TEN_SECONDS: '*/10 * * * * *', + EVERY_THIRTY_SECONDS: '*/30 * * * * *', + EVERY_MINUTE: '* * * * *', + EVERY_FIVE_MINUTES: '*/5 * * * *', + EVERY_TEN_MINUTES: '*/10 * * * *', + EVERY_THIRTY_MINUTES: '*/30 * * * *', + EVERY_HOUR: '0 * * * *', + EVERY_TWO_HOURS: '0 */2 * * *', + EVERY_THREE_HOURS: '0 */3 * * *', + EVERY_FOUR_HOURS: '0 */4 * * *', + EVERY_SIX_HOURS: '0 */6 * * *', + EVERY_TWELVE_HOURS: '0 */12 * * *', + EVERY_DAY_MIDNIGHT: '0 0 * * *', + EVERY_DAY_MORNING: '0 8 * * *', + EVERY_DAY_NOON: '0 12 * * *', + EVERY_DAY_AFTERNOON: '0 14 * * *', + EVERY_DAY_EVENING: '0 19 * * *' +}; \ No newline at end of file diff --git a/helpers/general.js b/helpers/general.js index 310d7fa..28f85ca 100644 --- a/helpers/general.js +++ b/helpers/general.js @@ -1,94 +1,94 @@ -if (typeof module !== 'undefined'){ - var fs = require('fs'), - path = require('path'), - request = require('request'); +if ( typeof module !== 'undefined' ){ + const fs = require( 'fs' ), + path = require( 'path' ), + request = require( 'request' ); } -var helpers = { +const helpers = { getTimestamp: function(){ - return Math.round((new Date()).getTime() / 1000); + return Math.round( ( new Date() ).getTime() / 1000 ); }, - randomFromArray: function(arr) { - return arr[Math.floor(Math.random()*arr.length)]; + randomFromArray: function( arr ){ + return arr[Math.floor( Math.random()*arr.length )]; }, - getRandomInt: function(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; + getRandomInt: function( min, max ){ + return Math.floor( Math.random() * ( max - min + 1 ) ) + min; }, - getRandomRange: function(min, max, fixed) { - return (Math.random() * (max - min) + min).toFixed(fixed) * 1; + getRandomRange: function( min, max, fixed ){ + return ( Math.random() * ( max - min ) + min ).toFixed( fixed ) * 1; }, - getRandomHex: function() { - return '#' + Math.random().toString(16).slice(2, 8).toUpperCase(); + getRandomHex: function(){ + return '#' + Math.random().toString( 16 ).slice( 2, 8 ).toUpperCase(); }, - shadeColor: function(color, percent) { + shadeColor: function( color, percent ){ // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors - var f = parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF; - return `#${(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1)}`; + let f = parseInt( color.slice( 1 ),16 ),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF; + return `#${( 0x1000000+( Math.round( ( t-R )*p )+R )*0x10000+( Math.round( ( t-G )*p )+G )*0x100+( Math.round( ( t-B )*p )+B ) ).toString( 16 ).slice( 1 )}`; }, - loadImageAssets: function(cb){ + loadImageAssets: function( cb ){ /* Load images from the assets folder */ - console.log('reading assets folder...') - var that = this; - fs.readFile('./.glitch-assets', 'utf8', function (err, data) { - if (err) { - console.log('error:', err); + console.log( 'reading assets folder...' ) + let that = this; + fs.readFile( './.glitch-assets', 'utf8', function ( err, data ){ + if ( err ){ + console.log( 'error:', err ); return false; } - data = data.split('\n'); - var data_json = JSON.parse('[' + data.join(',').slice(0, -1) + ']'), - deleted_images = data_json.reduce(function(filtered, data_img) { - if (data_img.deleted) { - var someNewValue = { name: data_img.name, newProperty: 'Foo' } - filtered.push(data_img.uuid); + data = data.split( '\n' ); + let data_json = JSON.parse( '[' + data.join( ',' ).slice( 0, -1 ) + ']' ), + deleted_images = data_json.reduce( function( filtered, data_img ){ + if ( data_img.deleted ){ + let someNewValue = { name: data_img.name, newProperty: 'Foo' } + filtered.push( data_img.uuid ); } return filtered; - }, []), + }, [] ), img_urls = []; - for (var i = 0, j = data.length; i < j; i++){ - if (data[i].length){ - var img_data = JSON.parse(data[i]), + for ( let i = 0, j = data.length; i < j; i++ ){ + if ( data[i].length ){ + let img_data = JSON.parse( data[i] ), image_url = img_data.url; - if (image_url && deleted_images.indexOf(img_data.uuid) === -1 && that.extension_check(image_url)){ - var file_name = that.get_filename_from_url(image_url).split('%2F')[1]; - // console.log(`- ${file_name}`); - img_urls.push(image_url); + if ( image_url && deleted_images.indexOf( img_data.uuid ) === -1 && that.extension_check( image_url ) ){ + let file_name = that.get_filename_from_url( image_url ).split( '%2F' )[1]; + // console.log( `- ${file_name}` ); + img_urls.push( image_url ); } } } - cb(null, img_urls); - }); + cb( null, img_urls ); + } ); }, - extensionCheck: function(url) { - var file_extension = path.extname(url).toLowerCase(), + extensionCheck: function( url ){ + let file_extension = path.extname( url ).toLowerCase(), extensions = ['.png', '.jpg', '.jpeg', '.gif']; - return extensions.indexOf(file_extension) !== -1; + return extensions.indexOf( file_extension ) !== -1; }, - getFilenameFromUrl: function(url) { - return url.substring(url.lastIndexOf('/') + 1); + getFilenameFromUrl: function( url ){ + return url.substring( url.lastIndexOf( '/' ) + 1 ); }, - loadImage: function(url, cb) { - console.log(`loading remote image: ${url} ...`); - request({url: url, encoding: null}, function (err, res, body) { - if (!err && res.statusCode == 200) { - var b64content = 'data:' + res.headers['content-type'] + ';base64,'; - console.log('image loaded...'); - cb(null, body.toString('base64')); + loadImage: function( url, cb ){ + console.log( `loading remote image: ${url} ...` ); + request( {url: url, encoding: null}, function ( err, res, body ){ + if ( !err && res.statusCode == 200 ){ + let b64content = 'data:' + res.headers['content-type'] + ';base64,'; + console.log( 'image loaded...' ); + cb( null, body.toString( 'base64' ) ); } else { - console.log('ERROR:', err); - cb(err); + console.log( 'ERROR:', err ); + cb( err ); } - }); + } ); }, - downloadFile: function(uri, filename, cb){ - request.head(uri, function(err, res, body){ - request(uri).pipe(fs.createWriteStream(filename)).on('close', cb); - }); + downloadFile: function( uri, filename, cb ){ + request.head( uri, function( err, res, body ){ + request( uri ).pipe( fs.createWriteStream( filename ) ).on( 'close', cb ); + } ); } }; -if (typeof module !== 'undefined'){ +if ( typeof module !== 'undefined' ){ /* This is to make the file usable both in node and on the front end. */ module.exports = helpers; } diff --git a/helpers/image-uploader.js b/helpers/image-uploader.js index d187e45..1381830 100644 --- a/helpers/image-uploader.js +++ b/helpers/image-uploader.js @@ -1,59 +1,58 @@ -var fs = require('fs'), - bot = require(__dirname + '/../bot/bot.js'), - NeoCities = require('neocities'), - use_neocities = false; +const fs = require( 'fs' ), + bot = require( __dirname + '/../bot/bot.js' ), + NeoCities = require( 'neocities' ); +let useNeocities = false; -if (process.env.NEOCITIES_USERNAME && process.env.NEOCITIES_PASSWORD){ - var neocities_api = new NeoCities(process.env.NEOCITIES_USERNAME, process.env.NEOCITIES_PASSWORD); - use_neocities = true; +if ( process.env.NEOCITIES_USERNAME && process.env.NEOCITIES_PASSWORD ){ + const neocitiesAPI = new NeoCities( process.env.NEOCITIES_USERNAME, process.env.NEOCITIES_PASSWORD ); + useNeocities = true; } module.exports = { - uploadImage: function(img_data, cb){ - var img_url = `${bot.bot_url}/${img_data.path}`, - img_name = img_data.path.replace('img/', ''); + uploadImage: function( img_data, cb ){ + let img_url = `${bot.bot_url}/${img_data.path}`, + img_name = img_data.path.replace( 'img/', '' ); - if (use_neocities){ + if ( useNeocities ){ /* First option, NeoCities. They offer 1GB for free, and with a - paid option ($5/month) you get 50GB. + paid option ( $5/month ) you get 50GB. https://neocities.org/supporter */ - neocities_api.upload([ - { + neocitiesAPI.upload( [ { name: img_name, path: `.data/img/${img_name}` - } - ], function(resp) { - console.log(resp); - var img_url = null; + } ], function( resp ) { + console.log( resp ); + img_url = null; - if (resp && resp.result === 'success'){ - fs.unlink(`.data/img/${img_name}`, function(err){ - if (err){ - console.log(err); + if ( resp && resp.result === 'success' ){ + fs.unlink( `.data/img/${img_name}`, function( err ){ + if ( err ){ + console.log( err ); } else{ - console.log('deleted local image'); + console.log( 'deleted local image' ); } - }); + } ); img_url = `https://${process.env.NEOCITIES_USERNAME}.neocities.org/${img_name}`; } - if (cb){ - cb(null, img_url, resp); + if ( cb ){ + cb( null, img_url, resp ); } - }); + } ); } else { /* Fall-back to local storage. It's only ~128 MB, so good luck! */ - if (cb){ - cb(null, img_url); + if ( cb ){ + cb( null, img_url ); } } } }; + diff --git a/helpers/keys.js b/helpers/keys.js index b01d97f..6c32054 100644 --- a/helpers/keys.js +++ b/helpers/keys.js @@ -1,25 +1,25 @@ -var fs = require('fs'), - util = require('util'), - generate_rsa_keypair = require('generate-rsa-keypair'), - pem = require('pem'), - pubkey_path = '.data/rsa/pubKey', - privkey_path = '.data/rsa/privKey'; +const fs = require( 'fs' ), + util = require( 'util' ), + generate_rsa_keypair = require( 'generate-rsa-keypair' ), + pem = require( 'pem' ), + pubkey_path = '.data/rsa/pubKey', + privkey_path = '.data/rsa/privKey'; module.exports = { - generateKeys: function(cb) { - console.log('generating keys...'); + generateKeys: function( cb ) { + console.log( 'generating keys...' ); try{ - fs.mkdirSync('.data/rsa'); - } catch(err){ /* noop */ } + fs.mkdirSync( '.data/rsa' ); + } catch( err ){ /* noop */ } - var pair = generate_rsa_keypair(); + var pair = generate_rsa_keypair( ); - fs.writeFileSync(privkey_path, pair.private); - fs.writeFileSync(pubkey_path, pair.public); + fs.writeFileSync( privkey_path, pair.private ); + fs.writeFileSync( pubkey_path, pair.public ); - if (cb){ - cb(null); + if ( cb ){ + cb( null ); } } }; diff --git a/package.json b/package.json index a11f38d..6599b6b 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "start": "node server.js" }, "dependencies": { + "cron": "^1.8.2", "express": "^4.16.3", "sqlite3": "^4.0.0", "express-handlebars": "^3.0.0", diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..d65f680 --- /dev/null +++ b/robots.txt @@ -0,0 +1,3 @@ +User-agent: MastoPeek v0.7.2 - https://mastopeek.app-dist.eu +User-agent: fediverse.space crawler +Disallow: / diff --git a/routes/admin.js b/routes/admin.js index da923b7..49d87cb 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -1,47 +1,47 @@ -var express = require('express'), - session = require('express-session'), - router = express.Router(), - moment = require('moment'), - db = require(__dirname + '/../helpers/db.js'), - bot = require(__dirname + '/../bot/bot.js'); +const express = require( 'express' ), + session = require( 'express-session' ), + router = express.Router(), + moment = require( 'moment' ), + db = require( __dirname + '/../helpers/db.js' ), + bot = require( __dirname + '/../bot/bot.js' ); -router.get('/', function (req, res) { - res.render('../views/admin.handlebars', { +router.get( '/', function ( req, res ) { + res.render( '../views/admin.handlebars', { project_name: process.env.PROJECT_DOMAIN, bot_avatar_url: process.env.BOT_AVATAR_URL, bot_username: process.env.BOT_USERNAME, bot_description: process.env.BOT_DESCRIPTION - }); -}); + } ); +} ); -router.get('/logout', function (req, res) { +router.get( '/logout', function ( req, res ) { req.session.is_admin = false; req.session.save(); - console.log('admin logged out'); - console.log(req.body); - res.redirect('/'); -}); + console.log( 'admin logged out' ); + console.log( req.body ); + res.redirect( '/' ); +} ); -router.post('/', function (req, res) { - if (process.env.ADMIN_PASSWORD && req.body.password){ - if (req.body.password === process.env.ADMIN_PASSWORD){ +router.post( '/', function ( req, res ) { + if ( process.env.ADMIN_PASSWORD && req.body.password ){ + if ( req.body.password === process.env.ADMIN_PASSWORD ){ req.session.is_admin = true; req.session.save(); - console.log('saving session...', req.session.is_admin); + console.log( 'saving session...', req.session.is_admin ); - req.session.save(function(err) { - console.log('admin logged in'); - res.redirect('/'); - }); + req.session.save( function( err ) { + console.log( 'admin logged in' ); + res.redirect( '/' ); + } ); } else{ req.session.is_admin = false; - console.log('failed login attempt'); - console.log(req.body); - res.redirect('/admin'); + console.log( 'failed login attempt' ); + console.log( req.body ); + res.redirect( '/admin' ); } } -}); +} ); module.exports = router; diff --git a/routes/bot-endpoint.js b/routes/bot-endpoint.js deleted file mode 100644 index 40a06bf..0000000 --- a/routes/bot-endpoint.js +++ /dev/null @@ -1,18 +0,0 @@ -var express = require('express'), - router = express.Router(), - grammar = require(__dirname + '/../tracery/tracery.js').grammar, - bot = require(__dirname + '/../bot/bot.js'); - -router.get('/', function (req, res) { - var content = grammar.flatten("#origin#"); - - bot.createPost({ - type: 'Note', // See www.w3.org/ns/activitystreams#objects - content: content - }, function(err, message){ - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(message)); - }); -}); - -module.exports = router; diff --git a/routes/bot.js b/routes/bot.js index 09c0a35..e35f851 100644 --- a/routes/bot.js +++ b/routes/bot.js @@ -1,24 +1,22 @@ -var fs = require('fs'), - url = require('url'), - util = require('util'), - bot = require(__dirname + '/../bot/bot.js'); +const fs = require( 'fs' ), + url = require( 'url' ), + util = require( 'util' ), + bot = require( __dirname + '/../bot/bot.js' ), + express = require( 'express' ), + router = express.Router( ); - -var express = require('express'), - router = express.Router(); - -router.get('/', function (req, res) { - var url_parts = url.parse(req.url, true); +router.get( '/', function( req, res ) { + const urlParts = url.parse( req.url, true ); - if (req.headers['user-agent'].indexOf('mastodon') !== -1 || (req.query.debug && req.query.debug !== '')){ - res.setHeader('Content-Type', 'application/json'); - // console.log(bot.info); - // res.send(JSON.stringify(bot.info)); - res.json(bot.info); + if ( req.headers['user-agent'].indexOf( 'mastodon' ) !== -1 || ( req.query.debug && req.query.debug !== '' ) ){ + res.setHeader( 'Content-Type', 'application/json' ); + // console.log( bot.info ); + // res.send( JSON.stringify( bot.info ) ); + res.json( bot.info ); } else{ - res.redirect('/'); + res.redirect( '/' ); } -}); +} ); module.exports = router; diff --git a/routes/delete-post.js b/routes/delete-post.js index e5f944d..c42845e 100644 --- a/routes/delete-post.js +++ b/routes/delete-post.js @@ -1,15 +1,15 @@ -var express = require( 'express' ), - router = express.Router(), - moment = require( 'moment' ), - bot = require( __dirname + '/../bot/bot.js' ), - db = require( __dirname + '/../helpers/db.js' ); +const express = require( 'express' ), + router = express.Router(), + moment = require( 'moment' ), + bot = require( __dirname + '/../bot/bot.js' ), + db = require( __dirname + '/../helpers/db.js' ); router.get( '/:id', function( req, res ) { - var is_admin = req.session.is_admin, - post_id = req.params.id; + let isAdmin = req.session.is_admin, + postID = req.params.id; - if ( is_admin ){ - if ( post_id === 'all' ){ + if ( isAdmin ){ + if ( postID === 'all' ){ console.log( { 'delete post': 'all of them' } ); @@ -38,14 +38,14 @@ router.get( '/:id', function( req, res ) { } else { console.log( { - 'delete post': post_id + 'delete post': postID } ); - db.deletePost( post_id, bot, function( err ){ + db.deletePost( postID, bot, function( err ){ if ( err ){ - console.log( `error deleting post ${post_id}`, err ); + console.log( `error deleting post ${postID}`, err ); } else { - console.log( `deleted post ${post_id}` ); + console.log( `deleted post ${postID}` ); } } ); res.redirect( '/' ); diff --git a/routes/feed.js b/routes/feed.js index dfdd568..ac50aff 100644 --- a/routes/feed.js +++ b/routes/feed.js @@ -1,9 +1,8 @@ -var express = require( 'express' ), - router = express.Router(), - RSS = require( 'rss' ), - moment = require( 'moment' ), - dbHelper = require( __dirname + '/../helpers/db.js' ); - +const express = require( 'express' ), + router = express.Router(), + RSS = require( 'rss' ), + moment = require( 'moment' ), + dbHelper = require( __dirname + '/../helpers/db.js' ); router.get( '/', function( req, res ) { let xml = ''; diff --git a/routes/inbox.js b/routes/inbox.js index 8cc8d1a..ec466bf 100644 --- a/routes/inbox.js +++ b/routes/inbox.js @@ -1,89 +1,87 @@ -var fs = require('fs'), - url = require('url'), - crypto = require('crypto'), - util = require('util'), - jsdom = require('jsdom'), - dbHelper = require(__dirname + '/../helpers/db.js'), - bot = require(__dirname + '/../bot/bot.js'); +const fs = require( 'fs' ), + url = require( 'url' ), + crypto = require( 'crypto' ), + util = require( 'util' ), + jsdom = require( 'jsdom' ), + dbHelper = require( __dirname + '/../helpers/db.js' ), + bot = require( __dirname + '/../bot/bot.js' ), + { JSDOM } = jsdom, + express = require( 'express' ), + router = express.Router( ); -const { JSDOM } = jsdom; - -var express = require('express'), - router = express.Router(); - -router.post('/', function (req, res) { - var url_parts = url.parse(req.url, true), +router.post( '/', function ( req, res ) { + let urlParts = url.parse( req.url, true ), payload = req.body; - console.log('/inbox'); + console.log( '/inbox' ); - console.log(payload.id); + console.log( payload.id ); /* TODO: Verify the message. */ - if (payload.type === 'Follow'){ + if ( payload.type === 'Follow' ){ - bot.accept(payload, function(err, payload, data){ - if (!err){ - dbHelper.saveFollower(payload, function(err, data){ - console.log(`new follower ${payload.actor} saved`); - }); + bot.accept( payload, function( err, payload, data ){ + if ( !err ){ + dbHelper.saveFollower( payload, function( err, data ){ + console.log( `new follower ${payload.actor} saved` ); + } ); } - res.status(200); - }); + res.status( 200 ); + } ); } - else if (payload.type === 'Undo'){ - bot.accept(payload, function(err, payload, data){ - if (!err){ - dbHelper.removeFollower(payload, function(err, data){ - console.log(`removed follower ${payload.actor}`); - }); + else if ( payload.type === 'Undo' ){ + bot.accept( payload, function( err, payload, data ){ + if ( !err ){ + dbHelper.removeFollower( payload, function( err, data ){ + console.log( `removed follower ${payload.actor}` ); + } ); } - res.status(200); - }); + res.status( 200 ); + } ); } - else if (payload.type === 'Create'){ - bot.accept(payload, function(err, payload, data){ - if (!err && payload.object && payload.object.content){ - var dom = new JSDOM(`