diff --git a/.glitch-assets b/.glitch-assets index 095c352..21daff0 100644 --- a/.glitch-assets +++ b/.glitch-assets @@ -12,3 +12,4 @@ {"name":"glitch-fediverse-bot-small-1024px.png","date":"2018-09-29T12:49:07.895Z","url":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot-small-1024px.png","type":"image/png","size":60397,"imageWidth":1024,"imageHeight":509,"thumbnail":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fthumbnails%2Fglitch-fediverse-bot-small-1024px.png","thumbnailWidth":330,"thumbnailHeight":165,"dominantColor":"rgb(252,252,252)","uuid":"iLCHg6pDTsAX7jkg"} {"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"} diff --git a/README.md b/README.md index 719aef9..1b8497a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -*WORK IN PROGRESS* -  @@ -40,7 +38,7 @@ You can use [ES6](http://es6-features.org/#Constants), you script files will be - https://neocities.org/api, https://neocities.org/supporter, https://github.com/neocities/neocities-node - https://www.digitalocean.com/products/spaces/, https://glitch.com/~digitalocean-spaces-example - Flickr API - - as a fallback, use the `.data/img` folder + - ~~as a fallback, use the `.data/img` folder~~ - reply to messages - add link to `/admin` - delete multiple posts at once (on the `/admin` page) diff --git a/bot.js b/bot.js index 5b929e4..44fede3 100644 --- a/bot.js +++ b/bot.js @@ -77,49 +77,61 @@ else{ create_post: function(options, cb){ var bot = this; - if ((!options.content || options.content.trim().length === 0 ) && (!options.thumbnail_url)){ - console.log('error: missing post content') + if ((!options.content || options.content.trim().length === 0 ) && !options.attachment ){ + console.log('error: no post content or attachments'); return false; } - var type = options.type || 'Note', + var post_type = options.type || 'Note', + post_description = options.description, post_date = moment().format(), post_in_reply_to = options.in_reply_to || null, - post_content = options.content || '', - post_thumbnail_url = options.thumbnail_url || ''; + post_content = options.content || options.url || '', + post_attachment = JSON.stringify(options.attachment) || '[]'; db.save_post({ - type: type, + type: post_type, content: post_content, - thumbnail_url: post_thumbnail_url + attachment: post_attachment }, function(err, data){ var post_id = data.lastID; - - var obj = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `${bot_url}/post/${post_id}`, - 'type': 'Create', - 'actor': `${bot_url}/bot`, - 'object': { + + var post_object; + + if ( post_type === 'Note' ){ + post_object = { 'id': `${bot_url}/post/${post_id}`, - 'type': type, + 'type': post_type, 'published': post_date, 'attributedTo': `${bot_url}/bot`, 'content': post_content, 'to': 'https://www.w3.org/ns/activitystreams#Public' + }; + + if (options.attachment){ + var attachments = []; + + options.attachment.forEach(function(attachment){ + attachments.push({ + 'type': 'Image', + 'content': attachment.content, + 'url': attachment.url + }); + }); + post_object.attachment = attachments; } - } - - if (options.thumbnail_url){ - obj.preview = { - 'type': 'Link', - 'href': options.thumbnail_url, - 'mediaType': 'image/png' - }; } + 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){ - obj.object.inReplyTo = post_in_reply_to; + post.object.inReplyTo = post_in_reply_to; } db.get_followers(function(err, followers){ @@ -130,7 +142,7 @@ else{ if (follower.url){ bot.sign_and_send({ follower: follower, - message: obj + message: post }, function(err, data){ }); @@ -140,7 +152,7 @@ else{ }); if (cb){ - cb(null, obj); + cb(null, post); } }); }, diff --git a/examples/generative-art-bot/README.md b/examples/generative-art-bot/README.md new file mode 100644 index 0000000..1ea9195 --- /dev/null +++ b/examples/generative-art-bot/README.md @@ -0,0 +1,14 @@ + + +# Generative art bot + +This is an example of a bot that generates images. It uses an image generator from [generative-art-bot](https://glitch.com/edit/#!/generative-art-bot). + +(Note that the code needed to be slightly modified to work with this project.) + +To try this example, copy the content of `examples/generative-art-bot/routes/bot-endpoint.js` to `routes/bot-endpoint.js` file and run your bot using its endpoint. + +By default, the images are stored in the `.data/img` folder. Glitch only provides ~128MB of storage (there are technical limitations to using the `assets` folder, which gives you additional ~500MB), but you can upload your images to NeoCities, which comes with a free 1GB of space, and has a paid plan ([$5/month](https://neocities.org/supporter)) that offers 50GB. + +Simply sign up for an account at [neocities.org](https://neocities.org/), and save your login information to the `.env` file as `NEOCITIES_USERNAME` and `NEOCITIES_PASSWORD`. + diff --git a/examples/generative-art-bot/routes/bot-endpoint.js b/examples/generative-art-bot/routes/bot-endpoint.js new file mode 100644 index 0000000..6df672f --- /dev/null +++ b/examples/generative-art-bot/routes/bot-endpoint.js @@ -0,0 +1,57 @@ +var express = require('express'), + router = express.Router(), + ColorScheme = require('color-scheme'), + generators = { + triangular_mesh: require(__dirname + '/../generators/triangular-mesh.js') + }, + grammar = require(__dirname + '/../tracery/tracery.js').grammar, + helpers = require(__dirname + '/../helpers/general.js'), + image_uploader = require(__dirname + '/../helpers/image-uploader.js'), + bot = require(__dirname + '/../bot.js'); + +router.get('/', function (req, res) { + var content = grammar.flatten("#origin#"), + scheme = new ColorScheme; + +/* + See https://www.npmjs.com/package/color-scheme#schemes on how to use ColorScheme. +*/ + + scheme.from_hex(helpers.get_random_hex().replace('#','')) + .scheme('mono') + .variation('hard'); + + generators.triangular_mesh({ + width: 800, + height: 360, + colors: scheme.colors() + }, function(err, img_data){ + // console.log({img_url: `${bot.bot_url}/${img_data.path}`}); + + var img_name = img_data.path.replace('img/', ''); + + image_uploader.upload_image(img_data, function(err, img_url, data){ + if (err){ + console.log(err); + } else { + // console.log(img_url); + + bot.create_post({ + type: 'Note', + content: content, + attachment: [ + { + url: img_url, + content: content // Image description here. + } + ] + }, function(err, message){ + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify(message)); + }); + } + }); + }); +}); + +module.exports = router; diff --git a/examples/images/README.md b/examples/images/README.md deleted file mode 100644 index cd12b83..0000000 --- a/examples/images/README.md +++ /dev/null @@ -1 +0,0 @@ -*WORK IN PROGRESS* diff --git a/examples/images/routes/bot-endpoint.js b/examples/images/routes/bot-endpoint.js deleted file mode 100644 index 83726c4..0000000 --- a/examples/images/routes/bot-endpoint.js +++ /dev/null @@ -1,40 +0,0 @@ -var express = require('express'), - router = express.Router(), - ColorScheme = require('color-scheme'), - generators = { - triangular_mesh: require(__dirname + '/../generators/triangular-mesh.js') - }, - grammar = require(__dirname + '/../tracery/tracery.js').grammar, - helpers = require(__dirname + '/../helpers/general.js'), - bot = require(__dirname + '/../bot.js'); - -router.get('/', function (req, res) { - var content = grammar.flatten("#origin#"); - - - var scheme = new ColorScheme; - - scheme.from_hex(helpers.get_random_hex().replace('#','')) - .scheme('mono') - .variation('pale'); - - generators.triangular_mesh({ - width: 1024, - height: 100, - colors: scheme.colors() - }, function(err, img_data){ - console.log({img_url: `${bot.bot_url}/${img_data.path}`}); - - bot.create_post({ - content: content, - thumbnail_url: `${bot.bot_url}/${img_data.path}` - }, function(err, message){ - - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(message)); - }); - - }); -}); - -module.exports = router; diff --git a/examples/tracery/routes/bot-endpoint.js b/examples/tracery/routes/bot-endpoint.js index 5333c33..bc071aa 100644 --- a/examples/tracery/routes/bot-endpoint.js +++ b/examples/tracery/routes/bot-endpoint.js @@ -7,6 +7,7 @@ router.get('/', function (req, res) { var content = grammar.flatten("#origin#"); bot.create_post({ + type: 'Note', // See www.w3.org/ns/activitystreams#objects content: content }, function(err, message){ res.setHeader('Content-Type', 'application/json'); diff --git a/helpers/db.js b/helpers/db.js index 54319ed..4beecd5 100644 --- a/helpers/db.js +++ b/helpers/db.js @@ -13,7 +13,7 @@ id INT NOT NULL AUTO_INCREMENT date DATETIME DEFAULT current_timestamp type VARCHAR(255) content TEXT -thumbnail_url TEXT +attachment TEXT [this is a stringified JSON, see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attachment] Followers table @@ -28,7 +28,7 @@ module.exports = { /* TODO: Rewrite this with promises. */ - db.run('CREATE TABLE IF NOT EXISTS Posts (id INTEGER PRIMARY KEY AUTOINCREMENT, date DATETIME DEFAULT current_timestamp, type VARCHAR(255), content TEXT, thumbnail_url 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), content TEXT, attachment TEXT)', function(err, data){ if (err){ console.log(err); } @@ -92,11 +92,14 @@ module.exports = { save_post: function(post_data, cb){ var post_type = post_data.type || 'Note', post_content = post_data.content || '', - post_thumbnail_url = post_data.thumbnail_url || ''; + post_attachment = post_data.attachment.toString() || '[]'; - db.serialize(function() { - db.run(`INSERT INTO Posts (type, content, thumbnail_url) VALUES ("${post_type}", "${post_content}", "${post_thumbnail_url}")`, 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, attachment) VALUES ('${post_type}', '${post_content}', '${post_attachment}')`, function(err, data){ + if (err){ + console.log(err); + } if (cb){ cb(err, this); } diff --git a/helpers/general.js b/helpers/general.js index 66653b9..eaccd02 100644 --- a/helpers/general.js +++ b/helpers/general.js @@ -1,3 +1,9 @@ +if (typeof module !== 'undefined'){ + var fs = require('fs'), + path = require('path'), + request = require('request'); +} + var helpers = { get_timestamp: function(){ return Math.round((new Date()).getTime() / 1000); @@ -18,6 +24,67 @@ var helpers = { // 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)}`; + }, + load_image_assets: 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); + 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); + } + 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]), + 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); + } + } + } + cb(null, img_urls); + }); + }, + extension_check: function(url) { + var file_extension = path.extname(url).toLowerCase(), + extensions = ['.png', '.jpg', '.jpeg', '.gif']; + return extensions.indexOf(file_extension) !== -1; + }, + get_filename_from_url: function(url) { + return url.substring(url.lastIndexOf('/') + 1); + }, + load_image: 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')); + } else { + console.log('ERROR:', err); + cb(err); + } + }); + }, + download_file: function(uri, filename, cb){ + request.head(uri, function(err, res, body){ + request(uri).pipe(fs.createWriteStream(filename)).on('close', cb); + }); } }; diff --git a/helpers/image-uploader.js b/helpers/image-uploader.js new file mode 100644 index 0000000..7fee522 --- /dev/null +++ b/helpers/image-uploader.js @@ -0,0 +1,58 @@ +var fs = require('fs'), + bot = require(__dirname + '/../bot.js'), + NeoCities = require('neocities'), + use_neocities = 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; +} + +module.exports = { + upload_image: function(img_data, cb){ + var img_url = `${bot.bot_url}/${img_data.path}`, + img_name = img_data.path.replace('img/', ''); + + if (use_neocities){ + /* + First option, NeoCities. They offer 1GB for free, and with a + paid option ($5/month) you get 50GB. + + https://neocities.org/supporter + + */ + neocities_api.upload([ + { + name: img_name, + path: `.data/img/${img_name}` + } + ], function(resp) { + console.log(resp); + var img_url = null; + + if (resp && resp.result === 'success'){ + fs.unlink(`.data/img/${img_name}`, function(err){ + if (err){ + console.log(err); + } + else{ + console.log('deleted local image'); + } + }); + + img_url = `https://${process.env.NEOCITIES_USERNAME}.neocities.org/${img_name}`; + } + 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); + } + } + } +}; + diff --git a/package.json b/package.json index 2fcfeb0..80126be 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "connect-sqlite3": "^0.9.11", "canvas": "^2.0.0-alpha.17", "color-scheme": "^1.0.1", - "gifencoder": "^1.1.0" + "gifencoder": "^1.1.0", + "neocities": "^0.0.3" }, "engines": { "node": "8.x" diff --git a/routes/bot-endpoint.js b/routes/bot-endpoint.js index 5333c33..bc071aa 100644 --- a/routes/bot-endpoint.js +++ b/routes/bot-endpoint.js @@ -7,6 +7,7 @@ router.get('/', function (req, res) { var content = grammar.flatten("#origin#"); bot.create_post({ + type: 'Note', // See www.w3.org/ns/activitystreams#objects content: content }, function(err, message){ res.setHeader('Content-Type', 'application/json'); diff --git a/routes/index.js b/routes/index.js index 467866a..1b5722c 100644 --- a/routes/index.js +++ b/routes/index.js @@ -25,6 +25,9 @@ router.get('/', function (req, res) { if (data && data.posts && data.posts.length > 0){ data.posts.forEach(function(post){ post.date_formatted = moment(post.date).fromNow(); + try{ + post.attachment = JSON.parse(post.attachment); + } catch(err){ /*noop*/ } }); } @@ -38,7 +41,7 @@ router.get('/', function (req, res) { if (page > 1 && page <= data.page_count){ show_previous_page = true; } - + res.render('../views/home.handlebars', { project_name: process.env.PROJECT_DOMAIN, bot_url: `https://${process.env.PROJECT_DOMAIN}.glitch.me/`, diff --git a/routes/post.js b/routes/post.js index 1511218..c1d51a6 100644 --- a/routes/post.js +++ b/routes/post.js @@ -8,6 +8,11 @@ router.get('/:id', function(req, res) { db.get_post(post_id, function(err, post_data){ if (post_data){ post_data.date_formatted = moment(post_data.date).fromNow();; + + try{ + post_data.attachment = JSON.parse(post_data.attachment); + } catch(err){ /*noop*/ } + res.render('../views/post.handlebars', { project_name: process.env.PROJECT_DOMAIN, diff --git a/server.js b/server.js index e6df6c4..7cb9acb 100644 --- a/server.js +++ b/server.js @@ -5,13 +5,12 @@ var app = require(__dirname + '/app.js'), // db.drop_table('Posts'); // db.drop_table('Followers'); +// db.get_followers(function(err, data){ +// console.log(data); +// }); + db.init(); var listener = app.listen(process.env.PORT, function() { console.log(`app is running on port ${listener.address().port}...`); }); - -// db.get_followers(function(err, data){ -// console.log(data); -// }); - diff --git a/sessions b/sessions index 270815d..e9c334c 100644 Binary files a/sessions and b/sessions differ diff --git a/shrinkwrap.yaml b/shrinkwrap.yaml index a0b49ab..aaeecb4 100644 --- a/shrinkwrap.yaml +++ b/shrinkwrap.yaml @@ -11,6 +11,7 @@ dependencies: generate-rsa-keypair: 0.1.2 gifencoder: 1.1.0 momentjs: 2.0.0 + neocities: 0.0.3 node-openssl-cert: 0.0.47 node-sass-middleware: 0.11.0 pem: 1.13.0 @@ -3253,6 +3254,12 @@ packages: node: '>= 0.6' resolution: integrity: sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= + /neocities/0.0.3: + dependencies: + form-data: 2.3.2 + dev: false + resolution: + integrity: sha1-uFXSRjC2Ec7zUUinedXH73EgDAs= /node-forge/0.6.49: dev: false resolution: @@ -5111,6 +5118,7 @@ specifiers: generate-rsa-keypair: ^0.1.2 gifencoder: ^1.1.0 momentjs: ^2.0.0 + neocities: ^0.0.3 node-openssl-cert: ^0.0.47 node-sass-middleware: ^0.11.0 pem: ^1.13.0 diff --git a/src/styles/layout.scss b/src/styles/_layout.scss similarity index 51% rename from src/styles/layout.scss rename to src/styles/_layout.scss index 8cb8d87..68b396d 100644 --- a/src/styles/layout.scss +++ b/src/styles/_layout.scss @@ -1,4 +1,13 @@ #about-bot.jumbotron{ border-top-left-radius: 0; border-top-right-radius: 0; -} \ No newline at end of file +} + +main{ + max-width: 720px; + margin: 0 auto; +} + +img{ + max-width: 100%; +} diff --git a/src/styles/_typography.scss b/src/styles/_typography.scss new file mode 100644 index 0000000..9132fb6 --- /dev/null +++ b/src/styles/_typography.scss @@ -0,0 +1 @@ +@import "variables"; diff --git a/src/styles/variables.scss b/src/styles/_variables.scss similarity index 100% rename from src/styles/variables.scss rename to src/styles/_variables.scss diff --git a/src/styles/styles.scss b/src/styles/styles.scss index ab10344..87b9098 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -1,3 +1,3 @@ -// @import "variables"; -// @import "typography"; +@import "variables"; +@import "typography"; @import "layout"; diff --git a/src/styles/typography.scss b/src/styles/typography.scss deleted file mode 100644 index ddee521..0000000 --- a/src/styles/typography.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "variables"; - -body{ - font-size: 30px; - color: $red; -} \ No newline at end of file diff --git a/views/home.handlebars b/views/home.handlebars index 7fc5308..bee948a 100644 --- a/views/home.handlebars +++ b/views/home.handlebars @@ -3,15 +3,17 @@ {{#each posts}}
{{this.content}}