diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09b0fe8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +sessions diff --git a/.glitch-assets b/.glitch-assets index 21daff0..7bde543 100644 --- a/.glitch-assets +++ b/.glitch-assets @@ -13,3 +13,8 @@ {"name":"glitch-fediverse-bot.png","date":"2018-09-29T12:49:07.963Z","url":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot.png","type":"image/png","size":58653,"imageWidth":1920,"imageHeight":1079,"thumbnail":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fthumbnails%2Fglitch-fediverse-bot.png","thumbnailWidth":330,"thumbnailHeight":186,"dominantColor":"rgb(252,252,252)","uuid":"0KNy0WXAgI1pZ0fd"} {"name":"glitch-fediverse-bot-960px.png","date":"2018-09-29T12:49:08.030Z","url":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot-960px.png","type":"image/png","size":50867,"imageWidth":960,"imageHeight":540,"thumbnail":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fthumbnails%2Fglitch-fediverse-bot-960px.png","thumbnailWidth":330,"thumbnailHeight":186,"dominantColor":"rgb(252,252,252)","uuid":"8tHyMsnvvVKkjFXF"} {"name":"glitch-fediverse-bot-with-image.png","date":"2018-10-03T21:11:53.744Z","url":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot-with-image.png","type":"image/png","size":469823,"imageWidth":2880,"imageHeight":1465,"thumbnail":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fthumbnails%2Fglitch-fediverse-bot-with-image.png","thumbnailWidth":330,"thumbnailHeight":168,"dominantColor":"rgb(252,252,252)","uuid":"oHDFFpP6XyCauPmh"} +{"name":"bot-replies.png","date":"2018-10-09T12:35:59.939Z","url":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fbot-replies.png","type":"image/png","size":186912,"imageWidth":2834,"imageHeight":1256,"thumbnail":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fthumbnails%2Fbot-replies.png","thumbnailWidth":330,"thumbnailHeight":147,"dominantColor":"rgb(236,244,244)","uuid":"sHZ0AbdbgCtnaQYM"} +{"uuid":"iLCHg6pDTsAX7jkg","deleted":true} +{"uuid":"0KNy0WXAgI1pZ0fd","deleted":true} +{"uuid":"8tHyMsnvvVKkjFXF","deleted":true} +{"name":"glitch-fediverse-bot.png","date":"2018-10-09T12:49:00.686Z","url":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot.png","type":"image/png","size":206001,"imageWidth":2854,"imageHeight":1398,"thumbnail":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fthumbnails%2Fglitch-fediverse-bot.png","thumbnailWidth":330,"thumbnailHeight":162,"dominantColor":"rgb(236,244,244)","uuid":"6oFnGGQy88GLx9Be"} diff --git a/README.md b/README.md index b273546..a097ce6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ **Note**: This project is under active development and not available for remixing, but you can [import it from GitHub](https://glitch.com/#!/import/github/fourtonfish/glitch-fediverse-bot). -![Glitch Fediverse bot](https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot-small-1024px.png?1538225347895) +![Glitch Fediverse bot](https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot.png) # Glitch Fediverse bot diff --git a/bot/README.md b/bot/README.md new file mode 100644 index 0000000..46ad3b0 --- /dev/null +++ b/bot/README.md @@ -0,0 +1,6 @@ +# Bot logic + +The files in the `/bot` folder contain the source code for the bot's behavior. + +- Inside `bot.js` you'll find internal methods, like `create_post` and `delete_post`. +- To change bot's responses to messages it receives, see `responses.js`. diff --git a/bot.js b/bot/bot.js similarity index 74% rename from bot.js rename to bot/bot.js index 44fede3..1f3ed37 100644 --- a/bot.js +++ b/bot/bot.js @@ -3,12 +3,13 @@ var fs = require('fs'), url = require('url'), util = require('util'), moment = require('moment'), - db = require(__dirname + '/helpers/db.js'), - keys = require(__dirname + '/helpers/keys.js'), + db = 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`; + bot_url = `https://${process.env.PROJECT_DOMAIN}.glitch.me`, + bot_compose_reply = require(__dirname + '/responses.js'); if (!fs.existsSync(public_key_path) || !fs.existsSync(private_key_path)) { keys.generate_keys(function(){ @@ -21,7 +22,7 @@ else{ module.exports = { bot_url: bot_url, - links: [ + links: [ // { // rel: 'http://webfinger.net/rel/profile-page', // type: 'text/html', @@ -74,6 +75,28 @@ else{ 'publicKeyPem': public_key } }, + compose_reply: bot_compose_reply, + send_reply: function(options, cb){ + var bot = this, + reply_to_username = ''; + + 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} `; + + console.log({reply_to_username}); + } catch(err){ /*noop*/ } + + bot.create_post({ + type: 'Note', + content: `
${options.payload.object.content}

${options.payload.object.url}

${options.reply_message}

`, + reply_message: `${reply_to_username} ${options.reply_message}`, + in_reply_to: options.payload.object.url + }, function(err, message){ + // console.log(err, message); + }); + }, create_post: function(options, cb){ var bot = this; @@ -86,6 +109,7 @@ else{ post_description = options.description, 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) || '[]'; @@ -104,7 +128,7 @@ else{ 'type': post_type, 'published': post_date, 'attributedTo': `${bot_url}/bot`, - 'content': post_content, + 'content': reply_message || post_content, 'to': 'https://www.w3.org/ns/activitystreams#Public' }; @@ -122,18 +146,20 @@ else{ } } + if (post_in_reply_to){ + post_object.inReplyTo = post_in_reply_to; + } + var post = { '@context': 'https://www.w3.org/ns/activitystreams', 'id': `${bot_url}/post/${post_id}`, 'type': 'Create', 'actor': `${bot_url}/bot`, 'object': post_object - } - - if (options.post_in_reply_to){ - post.object.inReplyTo = post_in_reply_to; } - + + console.log({post_in_reply_to}); + db.get_followers(function(err, followers){ if (followers){ console.log(`sending update to ${followers.length} follower(s)...`); @@ -179,8 +205,12 @@ else{ }); }, accept: function(payload, cb){ - var bot = this, - guid = crypto.randomBytes(16).toString('hex'); + var bot = this, + guid = crypto.randomBytes(16).toString('hex'); + + db.get_event(payload.id, function(err, data){ + // console.log('get_event', err, data); + bot.sign_and_send({ follower: { @@ -197,7 +227,37 @@ else{ if (cb){ cb(err, payload, data); } + console.log('saving event', payload.id) + db.save_event(payload.id); }); + + }); +// db.get_event(payload.id, function(err, data){ +// console.log('get_event', err, data); + +// if (!err && !data){ +// bot.sign_and_send({ +// follower: { +// url: payload.actor +// }, +// message: { +// '@context': 'https://www.w3.org/ns/activitystreams', +// 'id': `${bot.bot_url}/${guid}`, +// 'type': 'Accept', +// 'actor': `${bot.bot_url}/bot`, +// 'object': payload, +// } +// }, function(err, data){ +// if (cb){ +// cb(err, payload, data); +// } +// console.log('saving event', payload.id) +// db.save_event(payload.id); +// }); +// } else if (!err){ +// console.log('duplicate event'); +// } +// }); }, sign_and_send: function(options, cb){ var bot = this; diff --git a/bot/responses.js b/bot/responses.js new file mode 100644 index 0000000..24b2d8b --- /dev/null +++ b/bot/responses.js @@ -0,0 +1,43 @@ +/* + Here you can modify how the bot responds to messages it receives. +*/ + +module.exports = function(data, callback_function) { +/* + At the end of this function we need to pass an error message and a response text. + Let's set up some default values. +*/ + + var error = null, + response = 'Hello 👋'; + +/* + The data object this function receives looks like this: + + data = { + payload: 'The original data object.', + message_body: 'The content of the message sent to the bot.', + message_from: 'The URL of the message sender.' + } + + message_body and message_from come from the payload object, so we can access them more conveniently. If we need more details, we can get those from the payload object itself. + +*/ + + console.log(`new message from ${data.message_from}:`) + console.log(data.message_body); + +/* + We can modify the response text. +*/ + + if (data.message_body.toLowerCase().indexOf('hello') > -1){ + response = 'Hi 👋'; + } + + /* + Finally, we pass the error and reply message to the callback function that sends it to the author of the message that the bot received and saves it to the post database. + */ + + callback_function(error, response); +}; diff --git a/examples/replies/README.md b/examples/replies/README.md new file mode 100644 index 0000000..78f436c --- /dev/null +++ b/examples/replies/README.md @@ -0,0 +1,15 @@ +**Work in progress** + +![A bot replying to a message](https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fbot-replies.png?1539088559939) + +# Bot replies + +To use bot replies, update `bot/responses.js` with a function that returns a reply message. + + +TODO: + +- correctly dedupe events +- add support for private messages +- send notification when posting reply + diff --git a/examples/replies/bot/bot-replies.js b/examples/replies/bot/bot-replies.js new file mode 100644 index 0000000..24b2d8b --- /dev/null +++ b/examples/replies/bot/bot-replies.js @@ -0,0 +1,43 @@ +/* + Here you can modify how the bot responds to messages it receives. +*/ + +module.exports = function(data, callback_function) { +/* + At the end of this function we need to pass an error message and a response text. + Let's set up some default values. +*/ + + var error = null, + response = 'Hello 👋'; + +/* + The data object this function receives looks like this: + + data = { + payload: 'The original data object.', + message_body: 'The content of the message sent to the bot.', + message_from: 'The URL of the message sender.' + } + + message_body and message_from come from the payload object, so we can access them more conveniently. If we need more details, we can get those from the payload object itself. + +*/ + + console.log(`new message from ${data.message_from}:`) + console.log(data.message_body); + +/* + We can modify the response text. +*/ + + if (data.message_body.toLowerCase().indexOf('hello') > -1){ + response = 'Hi 👋'; + } + + /* + Finally, we pass the error and reply message to the callback function that sends it to the author of the message that the bot received and saves it to the post database. + */ + + callback_function(error, response); +}; diff --git a/helpers/db.js b/helpers/db.js index 4beecd5..d415360 100644 --- a/helpers/db.js +++ b/helpers/db.js @@ -12,6 +12,7 @@ Posts table id INT NOT NULL AUTO_INCREMENT date DATETIME DEFAULT current_timestamp type VARCHAR(255) +in_reply_to TEXT content TEXT attachment TEXT [this is a stringified JSON, see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attachment] @@ -20,30 +21,39 @@ Followers table url TEXT PRIMARY KEY date DATETIME DEFAULT current_timestamp +Events table + +id TEXT PRIMARY +date DATETIME DEFAULT current_timestamp + + */ module.exports = { init: function(cb){ db.serialize(function(){ /* - TODO: Rewrite this with promises. + TODO: Rewrite this with promises and callback support. */ - db.run('CREATE TABLE IF NOT EXISTS Posts (id INTEGER PRIMARY KEY AUTOINCREMENT, date DATETIME DEFAULT current_timestamp, type VARCHAR(255), content TEXT, attachment TEXT)', function(err, data){ + + db.run('CREATE TABLE IF NOT EXISTS Posts (id INTEGER PRIMARY KEY AUTOINCREMENT, date DATETIME DEFAULT current_timestamp, type VARCHAR(255), in_reply_to TEXT, content TEXT, attachment TEXT)', function(err, data){ + if (err){ + console.log(err); + } + }); + + db.run('CREATE TABLE IF NOT EXISTS Followers (url TEXT PRIMARY KEY, date DATETIME DEFAULT current_timestamp)', function(err, data){ if (err){ console.log(err); } - else{ - db.run('CREATE TABLE IF NOT EXISTS Followers (url TEXT PRIMARY KEY, date DATETIME DEFAULT current_timestamp)', function(err, data){ - // db.run('CREATE TABLE IF NOT EXISTS Followers (id INTEGER PRIMARY KEY AUTOINCREMENT, date DATETIME DEFAULT current_timestamp, url TEXT)', function(err, data){ - if (err){ - console.log(err); - } - else{ - console.log('DB ready...'); - } - }); + }); + + db.run('CREATE TABLE IF NOT EXISTS Events (id TEXT PRIMARY KEY, date DATETIME DEFAULT current_timestamp)', function(err, data){ + if (err){ + console.log(err); } }); + }); }, get_posts: function(options, cb){ @@ -58,10 +68,13 @@ module.exports = { */ db.all('SELECT COUNT(*) AS total_count FROM Posts', function(err, rows) { - var total_count = rows[0].total_count, - total_pages= Math.ceil(total_count/POSTS_PER_PAGE); - + var total_count = 0; + + if (rows){ + total_count = rows[0].total_count; + } + var total_pages = Math.ceil(total_count/POSTS_PER_PAGE); var db_query = `SELECT * from Posts ORDER BY date DESC LIMIT ${POSTS_PER_PAGE} OFFSET ${offset}`; db.all(db_query, function(err, rows) { @@ -71,7 +84,7 @@ module.exports = { page_count: total_pages, posts: rows }; - cb(null, db_return); + cb(err, db_return); } }); @@ -84,7 +97,7 @@ module.exports = { db.all(`SELECT * from Posts WHERE id=${post_id}`, function(err, rows) { if (cb){ var post_data = (rows ? rows[0] : null); - cb(null, post_data); + cb(err, post_data); } }); }); @@ -92,11 +105,12 @@ module.exports = { save_post: function(post_data, cb){ var post_type = post_data.type || 'Note', post_content = post_data.content || '', + in_reply_to = post_data.in_reply_to || '', post_attachment = post_data.attachment.toString() || '[]'; db.serialize(function() { // db.run(`INSERT INTO Posts (type, content, attachment) VALUES ("${post_type}", "${post_content}", "${post_attachment}")`, function(err, data){ - db.run(`INSERT INTO Posts (type, content, attachment) VALUES ('${post_type}', '${post_content}', '${post_attachment}')`, function(err, data){ + db.run(`INSERT INTO Posts (type, content, in_reply_to, attachment) VALUES ('${post_type}', '${post_content}', '${in_reply_to}', '${post_attachment}')`, function(err, data){ if (err){ console.log(err); } @@ -139,11 +153,54 @@ module.exports = { db.all("SELECT * from Followers ORDER BY date DESC", function(err, rows) { if (cb){ - cb(null, rows); + cb(err, rows); + } + }); + }); + }, + save_event: function(event_id, cb){ + db.serialize(function() { + db.run(`INSERT INTO Events (id) VALUES ('${event_id}')`, function(err, data){ + if (err){ + console.log(err); + } + if (cb){ + cb(err, this); + } + }); + }); + }, + get_event: function(event_id, cb){ + var data = []; + db.serialize(function(){ + db.all(`SELECT * from Events WHERE id='${event_id}'`, function(err, rows) { + if (cb){ + var data = (rows ? rows[0] : null); + cb(err, data); } }); }); }, + get_events: function(cb){ + db.serialize(function(){ + db.all('SELECT * FROM Events', function(err, rows) { + if (cb){ + cb(err, rows); + } + }); + }); + }, + get_replies: function(in_reply_to, cb){ + var data = []; + db.serialize(function(){ + db.all(`SELECT * from Posts WHERE in_reply_to='${in_reply_to}'`, function(err, rows) { + if (cb){ + var post_data = (rows ? rows[0] : null); + cb(err, post_data); + } + }); + }); + }, drop_table: function(table, cb){ db.serialize(function(){ if (table && exists) { diff --git a/package.json b/package.json index 80126be..1028890 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "canvas": "^2.0.0-alpha.17", "color-scheme": "^1.0.1", "gifencoder": "^1.1.0", - "neocities": "^0.0.3" + "neocities": "^0.0.3", + "jsdom": "^12.1.0" }, "engines": { "node": "8.x" diff --git a/routes/admin.js b/routes/admin.js index e573ad2..f061f52 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -3,7 +3,7 @@ var express = require('express'), router = express.Router(), moment = require('moment'), db = require(__dirname + '/../helpers/db.js'), - bot = require(__dirname + '/../bot.js'); + bot = require(__dirname + '/../bot/bot.js'); diff --git a/routes/bot-endpoint.js b/routes/bot-endpoint.js index bc071aa..41b105a 100644 --- a/routes/bot-endpoint.js +++ b/routes/bot-endpoint.js @@ -1,7 +1,7 @@ var express = require('express'), router = express.Router(), grammar = require(__dirname + '/../tracery/tracery.js').grammar, - bot = require(__dirname + '/../bot.js'); + bot = require(__dirname + '/../bot/bot.js'); router.get('/', function (req, res) { var content = grammar.flatten("#origin#"); diff --git a/routes/bot.js b/routes/bot.js index 6b055f0..d4775b6 100644 --- a/routes/bot.js +++ b/routes/bot.js @@ -1,7 +1,7 @@ var fs = require('fs'), url = require('url'), util = require('util'), - bot = require(__dirname + '/../bot.js'); + bot = require(__dirname + '/../bot/bot.js'); var express = require('express'), diff --git a/routes/delete-post.js b/routes/delete-post.js index 99e3021..851d968 100644 --- a/routes/delete-post.js +++ b/routes/delete-post.js @@ -1,7 +1,7 @@ var express = require('express'), router = express.Router(), moment = require('moment'), - bot = require(__dirname + '/../bot.js'), + bot = require(__dirname + '/../bot/bot.js'), db = require(__dirname + '/../helpers/db.js'); router.get('/:id', function(req, res) { diff --git a/routes/inbox.js b/routes/inbox.js index c58da10..e8ba23b 100644 --- a/routes/inbox.js +++ b/routes/inbox.js @@ -2,9 +2,11 @@ var fs = require('fs'), url = require('url'), crypto = require('crypto'), util = require('util'), + jsdom = require('jsdom'), db = require(__dirname + '/../helpers/db.js'), - bot = require(__dirname + '/../bot.js'); + bot = require(__dirname + '/../bot/bot.js'); +const { JSDOM } = jsdom; var express = require('express'), router = express.Router(); @@ -13,7 +15,10 @@ router.post('/', function (req, res) { var url_parts = url.parse(req.url, true), payload = req.body; - console.log('/inbox'); + console.log('/inbox'); + + console.log(payload.id); + /* TODO: Verify the message. */ @@ -21,19 +26,52 @@ router.post('/', function (req, res) { if (payload.type === 'Follow'){ bot.accept(payload, function(err, payload, data){ - db.save_follower(payload, function(err, data){ - console.log(`new follower ${payload.actor} saved`); - }); - + if (!err){ + db.save_follower(payload, function(err, data){ + console.log(`new follower ${payload.actor} saved`); + }); + } res.status(200); }); } else if (payload.type === 'Undo'){ bot.accept(payload, function(err, payload, data){ - db.remove_follower(payload, function(err, data){ - console.log(`removed follower ${payload.actor}`); - }); + if (!err){ + db.remove_follower(payload, function(err, data){ + console.log(`removed follower ${payload.actor}`); + }); + } + 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(`
${payload.object.content}
`), + message_body = ''; + try { + message_body = dom.window.document.body.firstChild.textContent; + + } catch(err){ /* noop */} + bot.compose_reply({ + payload: payload, + message_from: payload.actor, + message_body: message_body, + }, function(err, reply_message){ + if (!err){ + console.log(err); + console.log('sending reply...'); + bot.send_reply({ + payload: payload, + message_body: message_body, + reply_message: reply_message + }, function(err, data){ + + }); + } + }); + } res.status(200); }); } @@ -44,7 +82,7 @@ router.post('/', function (req, res) { } else{ console.log('payload', payload); - res.status(200); + res.status(200); } }); diff --git a/routes/index.js b/routes/index.js index 1b5722c..b362a88 100644 --- a/routes/index.js +++ b/routes/index.js @@ -3,7 +3,7 @@ var express = require('express'), router = express.Router(), moment = require('moment'), db = require(__dirname + '/../helpers/db.js'), - bot = require(__dirname + '/../bot.js'); + bot = require(__dirname + '/../bot/bot.js'); router.get('/', function (req, res) { // console.log(req.headers); @@ -22,6 +22,8 @@ router.get('/', function (req, res) { }, function(err, data){ // console.log(posts); + var no_posts = false; + if (data && data.posts && data.posts.length > 0){ data.posts.forEach(function(post){ post.date_formatted = moment(post.date).fromNow(); @@ -29,6 +31,8 @@ router.get('/', function (req, res) { post.attachment = JSON.parse(post.attachment); } catch(err){ /*noop*/ } }); + } else { + no_posts = true; } var show_next_page = false, @@ -42,6 +46,8 @@ router.get('/', function (req, res) { show_previous_page = true; } + console.log(data.page_count, data.page_count > 1) + res.render('../views/home.handlebars', { project_name: process.env.PROJECT_DOMAIN, bot_url: `https://${process.env.PROJECT_DOMAIN}.glitch.me/`, @@ -54,6 +60,7 @@ router.get('/', function (req, res) { post_count: data.post_count, page_count: data.page_count, posts: data.posts, + no_posts: no_posts, current_page: page, show_pagination: data.page_count > 1, next_page: page + 1, diff --git a/routes/outbox.js b/routes/outbox.js index 0434ffc..8078b94 100644 --- a/routes/outbox.js +++ b/routes/outbox.js @@ -1,7 +1,7 @@ var fs = require('fs'), url = require('url'), util = require('util'), - bot = require(__dirname + '/../bot.js'); + bot = require(__dirname + '/../bot/bot.js'); var express = require('express'), router = express.Router(); diff --git a/routes/pubsub.js b/routes/pubsub.js index 3ee1a49..bae74c5 100644 --- a/routes/pubsub.js +++ b/routes/pubsub.js @@ -1,7 +1,7 @@ var fs = require('fs'), url = require('url'), util = require('util'), - bot = require(__dirname + '/../bot.js'); + bot = require(__dirname + '/../bot/bot.js'); var express = require('express'), router = express.Router(); diff --git a/routes/salmon.js b/routes/salmon.js index fef2bd8..1db92e9 100644 --- a/routes/salmon.js +++ b/routes/salmon.js @@ -1,7 +1,7 @@ var fs = require('fs'), url = require('url'), util = require('util'), - bot = require(__dirname + '/../bot.js'); + bot = require(__dirname + '/../bot/bot.js'); var express = require('express'), router = express.Router(); diff --git a/routes/webhook.js b/routes/webhook.js index adb63dc..1937139 100644 --- a/routes/webhook.js +++ b/routes/webhook.js @@ -1,7 +1,7 @@ var fs = require('fs'), url = require('url'), util = require('util'), - bot = require(__dirname + '/../bot.js'); + bot = require(__dirname + '/../bot/bot.js'); var express = require('express'), router = express.Router(); diff --git a/routes/well-known.js b/routes/well-known.js index e7c9881..848f08e 100644 --- a/routes/well-known.js +++ b/routes/well-known.js @@ -1,7 +1,7 @@ var fs = require('fs'), url = require('url'), util = require('util'), - bot = require(__dirname + '/../bot.js'); + bot = require(__dirname + '/../bot/bot.js'); var express = require('express'), router = express.Router(); diff --git a/server.js b/server.js index 7cb9acb..b537f75 100644 --- a/server.js +++ b/server.js @@ -2,14 +2,23 @@ var app = require(__dirname + '/app.js'), load_keys = require(__dirname + '/helpers/keys.js'), db = require(__dirname + '/helpers/db.js'); +db.init(); + // db.drop_table('Posts'); // db.drop_table('Followers'); +// db.drop_table('Events'); // db.get_followers(function(err, data){ -// console.log(data); +// console.log('Followers:', data); // }); -db.init(); +// db.get_posts(function(err, data){ +// console.log('Posts:', data); +// }); + +// db.get_events(function(err, data){ +// console.log('Events:', data); +// }); var listener = app.listen(process.env.PORT, function() { console.log(`app is running on port ${listener.address().port}...`); diff --git a/sessions b/sessions deleted file mode 100644 index b4fc7e2..0000000 Binary files a/sessions and /dev/null differ diff --git a/shrinkwrap.yaml b/shrinkwrap.yaml index aaeecb4..e9f2079 100644 --- a/shrinkwrap.yaml +++ b/shrinkwrap.yaml @@ -10,6 +10,7 @@ dependencies: express-session: 1.15.6 generate-rsa-keypair: 0.1.2 gifencoder: 1.1.0 + jsdom: 12.1.0 momentjs: 2.0.0 neocities: 0.0.3 node-openssl-cert: 0.0.47 @@ -28,6 +29,10 @@ packages: hasBin: true resolution: integrity: sha512-Y7vfi3I5oMOYIr+WxV8NZxDSwcbNgzdKYsTNInmycOq9bUYwGg9ryu57Wg5NLmCjqdFPNUmpMBo3kSJN9tCbXg== + /abab/2.0.0: + dev: false + resolution: + integrity: sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w== /abbrev/1.1.1: dev: false resolution: @@ -47,6 +52,13 @@ packages: dev: false resolution: integrity: sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg== + /acorn-globals/4.3.0: + dependencies: + acorn: 6.0.2 + acorn-walk: 6.1.0 + dev: false + resolution: + integrity: sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw== /acorn-node/1.5.2: dependencies: acorn: 5.7.3 @@ -55,6 +67,12 @@ packages: dev: false resolution: integrity: sha512-krFKvw/d1F17AN3XZbybIUzEY4YEPNiGo05AfP3dBlfVKrMHETKpgjpuZkSF8qDNt9UkQcqj7am8yJLseklCMg== + /acorn-walk/6.1.0: + dev: false + engines: + node: '>=0.4.0' + resolution: + integrity: sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg== /acorn/5.7.3: dev: false engines: @@ -62,6 +80,13 @@ packages: hasBin: true resolution: integrity: sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== + /acorn/6.0.2: + dev: false + engines: + node: '>=0.4.0' + hasBin: true + resolution: + integrity: sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg== /activitystrea.ms/2.1.3: dependencies: activitystreams-context: 3.1.0 @@ -176,6 +201,10 @@ packages: node: '>=0.10.0' resolution: integrity: sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + /array-equal/1.0.0: + dev: false + resolution: + integrity: sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= /array-filter/0.0.1: dev: false resolution: @@ -254,6 +283,10 @@ packages: dev: false resolution: integrity: sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= + /async-limiter/1.0.0: + dev: false + resolution: + integrity: sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== /async/1.5.2: dev: false resolution: @@ -841,6 +874,10 @@ packages: hasBin: true resolution: integrity: sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA== + /browser-process-hrtime/0.1.3: + dev: false + resolution: + integrity: sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw== /browser-resolve/1.11.3: dependencies: resolve: 1.1.7 @@ -1441,6 +1478,16 @@ packages: dev: false resolution: integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + /cssom/0.3.4: + dev: false + resolution: + integrity: sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog== + /cssstyle/1.1.1: + dependencies: + cssom: 0.3.4 + dev: false + resolution: + integrity: sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog== /currently-unhandled/0.4.1: dependencies: array-find-index: 1.0.2 @@ -1457,6 +1504,14 @@ packages: node: '>=0.10' resolution: integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + /data-urls/1.0.1: + dependencies: + abab: 2.0.0 + whatwg-mimetype: 2.2.0 + whatwg-url: 7.0.0 + dev: false + resolution: + integrity: sha512-0HdcMZzK6ubMUnsMmQmG0AcLQPvbvb47R0+7CCZQCYgcd8OUWG91CG7sM6GoXgjz+WLl4ArFzHtBMy/QqSF4eg== /date-now/0.1.4: dev: false resolution: @@ -1485,6 +1540,10 @@ packages: node: '>=4.0.0' resolution: integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + /deep-is/0.1.3: + dev: false + resolution: + integrity: sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= /define-properties/1.1.3: dependencies: object-keys: 1.0.12 @@ -1621,6 +1680,12 @@ packages: npm: '>=1.2' resolution: integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + /domexception/1.0.1: + dependencies: + webidl-conversions: 4.0.2 + dev: false + resolution: + integrity: sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== /duplexer2/0.1.4: dependencies: readable-stream: 2.3.6 @@ -1701,6 +1766,33 @@ packages: node: '>=0.8.0' resolution: integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + /escodegen/1.11.0: + dependencies: + esprima: 3.1.3 + estraverse: 4.2.0 + esutils: 2.0.2 + optionator: 0.8.2 + dev: false + engines: + node: '>=4.0' + hasBin: true + optionalDependencies: + source-map: 0.6.1 + resolution: + integrity: sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw== + /esprima/3.1.3: + dev: false + engines: + node: '>=4' + hasBin: true + resolution: + integrity: sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= + /estraverse/4.2.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= /esutils/2.0.2: dev: false engines: @@ -1900,6 +1992,10 @@ packages: dev: false resolution: integrity: sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + /fast-levenshtein/2.0.6: + dev: false + resolution: + integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= /filename-regex/2.0.1: dev: false engines: @@ -2292,6 +2388,12 @@ packages: dev: false resolution: integrity: sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== + /html-encoding-sniffer/1.0.2: + dependencies: + whatwg-encoding: 1.0.5 + dev: false + resolution: + integrity: sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== /htmlescape/1.1.1: dev: false engines: @@ -2692,6 +2794,38 @@ packages: optional: true resolution: integrity: sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + /jsdom/12.1.0: + dependencies: + abab: 2.0.0 + acorn: 5.7.3 + acorn-globals: 4.3.0 + array-equal: 1.0.0 + cssom: 0.3.4 + cssstyle: 1.1.1 + data-urls: 1.0.1 + domexception: 1.0.1 + escodegen: 1.11.0 + html-encoding-sniffer: 1.0.2 + nwsapi: 2.0.9 + parse5: 5.1.0 + pn: 1.1.0 + request: 2.88.0 + request-promise-native: /request-promise-native/1.0.5/request@2.88.0 + saxes: 3.1.3 + symbol-tree: 3.2.2 + tough-cookie: 2.4.3 + w3c-hr-time: 1.0.1 + webidl-conversions: 4.0.2 + whatwg-encoding: 1.0.5 + whatwg-mimetype: 2.2.0 + whatwg-url: 7.0.0 + ws: 6.0.0 + xml-name-validator: 3.0.0 + dev: false + engines: + node: '>=8' + resolution: + integrity: sha512-1lrrgWhI5zN5B5p88xihrhfeAbdagMUXEN8Z+8l5f33k5IMZYDxIDJaPXKOOBF4xAt6hVyE/HrBbUYGjSrZlvg== /jsesc/0.5.0: dev: false hasBin: true @@ -2829,6 +2963,15 @@ packages: node: '>=0.10.0' resolution: integrity: sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= + /levn/0.3.0: + dependencies: + prelude-ls: 1.1.2 + type-check: 0.3.2 + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= /load-json-file/1.1.0: dependencies: graceful-fs: 4.1.11 @@ -2858,6 +3001,10 @@ packages: dev: false resolution: integrity: sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ== + /lodash.sortby/4.7.0: + dev: false + resolution: + integrity: sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= /lodash/3.10.1: dev: false resolution: @@ -3418,6 +3565,10 @@ packages: node: '>=0.10.0' resolution: integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + /nwsapi/2.0.9: + dev: false + resolution: + integrity: sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ== /oauth-sign/0.8.2: dev: false resolution: @@ -3511,6 +3662,19 @@ packages: dev: false resolution: integrity: sha1-2j6nRob6IaGaERwybpDrFaAZZoY= + /optionator/0.8.2: + dependencies: + deep-is: 0.1.3 + fast-levenshtein: 2.0.6 + levn: 0.3.0 + prelude-ls: 1.1.2 + type-check: 0.3.2 + wordwrap: 1.0.0 + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= /os-browserify/0.1.2: dev: false resolution: @@ -3597,6 +3761,10 @@ packages: node: '>=0.10.0' resolution: integrity: sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + /parse5/5.1.0: + dev: false + resolution: + integrity: sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== /parseurl/1.3.2: dev: false engines: @@ -3710,12 +3878,22 @@ packages: node: '>= 0.4.0' resolution: integrity: sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8= + /pn/1.1.0: + dev: false + resolution: + integrity: sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== /posix-character-classes/0.1.1: dev: false engines: node: '>=0.10.0' resolution: integrity: sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + /prelude-ls/1.1.2: + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= /prepare-response/1.1.3: dependencies: mime: 1.6.0 @@ -3798,6 +3976,12 @@ packages: dev: false resolution: integrity: sha1-wNWmOycYgArY4esPpSachN1BhF4= + /punycode/2.1.1: + dev: false + engines: + node: '>=6' + resolution: + integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== /qs/6.5.1: dev: false engines: @@ -4047,6 +4231,32 @@ packages: node: '>=0.10.0' resolution: integrity: sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + /request-promise-core/1.1.1/request@2.88.0: + dependencies: + lodash: 4.17.11 + request: 2.88.0 + dev: false + engines: + node: '>=0.10.0' + id: registry.npmjs.org/request-promise-core/1.1.1 + peerDependencies: + request: ^2.34 + resolution: + integrity: sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY= + /request-promise-native/1.0.5/request@2.88.0: + dependencies: + request: 2.88.0 + request-promise-core: /request-promise-core/1.1.1/request@2.88.0 + stealthy-require: 1.1.1 + tough-cookie: 2.4.3 + dev: false + engines: + node: '>=0.12.0' + id: registry.npmjs.org/request-promise-native/1.0.5 + peerDependencies: + request: ^2.34 + resolution: + integrity: sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU= /request/2.87.0: dependencies: aws-sign2: 0.7.0 @@ -4196,6 +4406,12 @@ packages: dev: false resolution: integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + /saxes/3.1.3: + dependencies: + xmlchars: 1.3.1 + dev: false + resolution: + integrity: sha512-Nc5DXc5A+m3rUDtkS+vHlBWKT7mCKjJPyia7f8YMW773hsXVv2wEHQZGE0zs4+5PLwz9U5Sbl/94Cnd9vHV7Bg== /scss-tokenizer/0.2.3: dependencies: js-base64: 2.4.9 @@ -4484,6 +4700,12 @@ packages: dev: false resolution: integrity: sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== + /stealthy-require/1.1.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= /stream-browserify/2.0.1: dependencies: inherits: 2.0.3 @@ -4595,6 +4817,10 @@ packages: node: '>=0.8.0' resolution: integrity: sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + /symbol-tree/3.2.2: + dev: false + resolution: + integrity: sha1-rifbOPZgp64uHDt9G8KQgZuFGeY= /syntax-error/1.4.0: dependencies: acorn-node: 1.5.2 @@ -4706,6 +4932,12 @@ packages: node: '>=0.8' resolution: integrity: sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + /tr46/1.0.1: + dependencies: + punycode: 2.1.1 + dev: false + resolution: + integrity: sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= /tracery-grammar/2.7.3: dev: false resolution: @@ -4743,6 +4975,14 @@ packages: optional: true resolution: integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + /type-check/0.3.2: + dependencies: + prelude-ls: 1.1.2 + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= /type-is/1.6.16: dependencies: media-typer: 0.3.0 @@ -4969,6 +5209,12 @@ packages: dev: false resolution: integrity: sha512-OIon2MWA21ZO42UBsTa5DuMsk5zv72DxMdQNvLsPN1M9GrjVTovn3LgWUZdPVnKBpdWhqWV7Mfbq/Sh0vkHIBw== + /w3c-hr-time/1.0.1: + dependencies: + browser-process-hrtime: 0.1.3 + dev: false + resolution: + integrity: sha1-gqwr/2PZUOqeMYmlimViX+3xkEU= /watchify/3.11.0: dependencies: anymatch: 1.3.2 @@ -4982,6 +5228,28 @@ packages: hasBin: true resolution: integrity: sha512-7jWG0c3cKKm2hKScnSAMUEUjRJKXUShwMPk0ASVhICycQhwND3IMAdhJYmc1mxxKzBUJTSF5HZizfrKrS6BzkA== + /webidl-conversions/4.0.2: + dev: false + resolution: + integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + /whatwg-encoding/1.0.5: + dependencies: + iconv-lite: 0.4.24 + dev: false + resolution: + integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + /whatwg-mimetype/2.2.0: + dev: false + resolution: + integrity: sha512-5YSO1nMd5D1hY3WzAQV3PzZL83W3YeyR1yW9PcH26Weh1t+Vzh9B6XkDh7aXm83HBZ4nSMvkjvN2H2ySWIvBgw== + /whatwg-url/7.0.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + dev: false + resolution: + integrity: sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ== /which-module/1.0.0: dev: false resolution: @@ -5017,6 +5285,10 @@ packages: node: '>=0.4.0' resolution: integrity: sha1-o9XabNXAvAAI03I0u68b7WMFkQc= + /wordwrap/1.0.0: + dev: false + resolution: + integrity: sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= /wrap-ansi/2.1.0: dependencies: string-width: 1.0.2 @@ -5031,10 +5303,24 @@ packages: dev: false resolution: integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + /ws/6.0.0: + dependencies: + async-limiter: 1.0.0 + dev: false + resolution: + integrity: sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w== + /xml-name-validator/3.0.0: + dev: false + resolution: + integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== /xml/1.0.1: dev: false resolution: integrity: sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= + /xmlchars/1.3.1: + dev: false + resolution: + integrity: sha512-tGkGJkN8XqCod7OT+EvGYK5Z4SfDQGD30zAa58OcnAa0RRWgzUEK72tkXhsX1FZd+rgnhRxFtmO+ihkp8LHSkw== /xmldom/0.1.19: dev: false engines: @@ -5117,6 +5403,7 @@ specifiers: express-session: ^1.15.6 generate-rsa-keypair: ^0.1.2 gifencoder: ^1.1.0 + jsdom: ^12.1.0 momentjs: ^2.0.0 neocities: ^0.0.3 node-openssl-cert: ^0.0.47 diff --git a/src/styles/_layout.scss b/src/styles/_layout.scss index 68b396d..fe809c4 100644 --- a/src/styles/_layout.scss +++ b/src/styles/_layout.scss @@ -1,6 +1,5 @@ -#about-bot.jumbotron{ - border-top-left-radius: 0; - border-top-right-radius: 0; +body{ + background: #eff0f2; } main{ @@ -11,3 +10,42 @@ main{ img{ max-width: 100%; } + +blockquote{ + /* Courtesy of css-tricks.com. */ + background: #f9f9f9; + border-left: 10px solid #ccc; + // max-width: $max-content-width; + margin: 2em auto; + padding: 0.5em 1em 1em 0; + quotes: "\201C""\201D""\2018""\2019"; + + .col-md-6 &{ + padding: 1em; + } + + &:before { + color: #ccc; + content: '“'; + font-size: 4em; + line-height: 0.1em; + margin-right: 0.25em; + vertical-align: -0.4em; + } + + p { + line-height: 1.5em; + padding: 0 40px; + } +} + +.card-footer{ + a.post-date{ + color: #6c757d; + } +} + +#about-bot.jumbotron{ + border-top-left-radius: 0; + border-top-right-radius: 0; +} diff --git a/views/home.handlebars b/views/home.handlebars index bee948a..720692c 100644 --- a/views/home.handlebars +++ b/views/home.handlebars @@ -1,6 +1,11 @@
{{> header }} + {{#if no_posts}} +

This bot hasn't posted yet.

+ {{/if}} + + {{#each posts}}
{{#if this.attachment}} @@ -11,13 +16,13 @@ {{#equals this.type "Note"}}
-

{{this.content}}

+

{{{this.content}}}

{{/equals}}
diff --git a/views/post.handlebars b/views/post.handlebars index c4a9523..ce6019e 100644 --- a/views/post.handlebars +++ b/views/post.handlebars @@ -5,7 +5,7 @@
{{post.date}}
{{#if post.content}} -

{{post.content}}

+

{{{post.content}}}

{{/if}} {{#each post.attachment}} @@ -16,9 +16,9 @@ {{/equals}}

- Main page + Main page {{#if is_admin}} - Delete + Delete {{/if}}