├── .gitignore ├── .rnd ├── src ├── styles │ ├── styles.scss │ ├── _variables.scss │ ├── _layout.scss │ └── _typography.scss └── scripts │ └── scripts.js ├── robots.txt ├── handlebars-helpers ├── equals.js ├── not_equals.js └── for.js ├── views ├── profile.handlebars ├── 404.handlebars ├── partials │ └── header.handlebars ├── about.handlebars ├── admin.handlebars ├── post.handlebars ├── home.handlebars └── layouts │ └── main.handlebars ├── bot ├── README.md ├── responses.js ├── script.js └── bot.js ├── examples ├── tracery │ ├── README.md │ └── script.js ├── replies │ ├── README.md │ └── bot │ │ └── responses.js └── generative-art-bot │ ├── README.md │ └── script.js ├── tracery ├── tracery.js └── grammar.json ├── watch.json ├── routes ├── webhook.js ├── outbox.js ├── pubsub.js ├── salmon.js ├── well-known.js ├── bot.js ├── admin.js ├── post.js ├── delete-post.js ├── inbox.js ├── index.js └── feed.js ├── public ├── humans.txt └── libs │ └── bootstrap │ └── bootstrap.min.js ├── helpers ├── keys.js ├── cron-schedules.js ├── general.js ├── colorbrewer.js └── db.js ├── LICENSE.md ├── package.json ├── server.js ├── README.md ├── app.js ├── generators └── joy-division.js └── .glitch-assets /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | sessions 3 | -------------------------------------------------------------------------------- /.rnd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/botwiki/fediverse-bot/HEAD/.rnd -------------------------------------------------------------------------------- /src/styles/styles.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | @import "typography"; 3 | @import "layout"; 4 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: MastoPeek v0.7.2 - https://mastopeek.app-dist.eu 2 | User-agent: fediverse.space crawler 3 | Disallow: / 4 | -------------------------------------------------------------------------------- /handlebars-helpers/equals.js: -------------------------------------------------------------------------------- 1 | module.exports = function(arg1, arg2, options){ 2 | return (arg1 == arg2) ? options.fn(this) : options.inverse(this); 3 | } -------------------------------------------------------------------------------- /handlebars-helpers/not_equals.js: -------------------------------------------------------------------------------- 1 | module.exports = function(arg1, arg2, options){ 2 | console.log(arg1, arg2); 3 | return (arg1 != arg2) ? options.fn(this) : options.inverse(this); 4 | } -------------------------------------------------------------------------------- /handlebars-helpers/for.js: -------------------------------------------------------------------------------- 1 | module.exports = function(from, to, incr, block){ 2 | var accum = ''; 3 | for(var i = from; i <= to; i += incr) 4 | accum += block.fn(i); 5 | return accum; 6 | } -------------------------------------------------------------------------------- /views/profile.handlebars: -------------------------------------------------------------------------------- 1 |
4 | Page not found. 5 |
6 |7 | Main page 8 |
9 |@bot@{{project_name}}.glitch.me
11 | {{{bot_description}}}
12 |9 | Work in progress. See related GitHub repo. 10 |
11 |14 | Main page 15 |
16 |21 | Main page 22 | {{#if is_admin}} 23 | Delete 24 | {{/if}} 25 |
26 |This bot hasn't posted yet.
5 | {{/if}} 6 | {{#each posts}} 7 |{{{this.content}}}
18 |${ options.payload.object.content }${ options.payload.object.url }
${ options.reply_message }
`, 99 | reply_message: `${ replyToUsername } ${ options.reply_message }`, 100 | in_reply_to: options.payload.object.url 101 | }, (err, message) => { 102 | // console.log(err, message); 103 | }); 104 | }, 105 | createPost: (options, cb) => { 106 | let bot = this; 107 | 108 | if ((!options.content || options.content.trim().length === 0) && !options.attachment){ 109 | console.log('error: no post content or attachments'); 110 | return false; 111 | } 112 | 113 | let postType = options.type || 'Note', 114 | postDescription = options.description, 115 | postDate = moment().format(), 116 | postInReplyTo = options.in_reply_to || null, 117 | replyMessage = options.reply_message || null, 118 | postContent = options.content || options.url || '', 119 | postAttachment = JSON.stringify(options.attachment) || '[]'; 120 | 121 | dbHelper.savePost({ 122 | type: postType, 123 | content: postContent, 124 | attachment: postAttachment 125 | }, (err, data) => { 126 | let postID = data.lastID; 127 | 128 | let postObject; 129 | 130 | if (postType === 'Note'){ 131 | postObject = { 132 | 'id': `${ botUrl }/post/${ postID }`, 133 | 'type': postType, 134 | 'published': postDate, 135 | 'attributedTo': `${ botUrl }/bot`, 136 | 'content': replyMessage || postContent, 137 | 'to': 'https://www.w3.org/ns/activitystreams#Public' 138 | }; 139 | 140 | if (options.attachment){ 141 | let attachments = []; 142 | 143 | options.attachment.forEach((attachment) => { 144 | attachments.push({ 145 | 'type': 'Image', 146 | 'content': attachment.content, 147 | 'url': attachment.url 148 | }); 149 | }); 150 | postObject.attachment = attachments; 151 | } 152 | } 153 | 154 | if (postInReplyTo){ 155 | postObject.inReplyTo = postInReplyTo; 156 | } 157 | 158 | let post = { 159 | '@context': 'https://www.w3.org/ns/activitystreams', 160 | 'id': `${ botUrl }/post/${ postID }`, 161 | 'type': 'Create', 162 | 'actor': `${ botUrl }/bot`, 163 | 'object': postObject 164 | } 165 | 166 | console.log({postInReplyTo}); 167 | 168 | dbHelper.getFollowers((err, followers) => { 169 | if (followers){ 170 | console.log(`sending update to ${ followers.length} follower(s)...`); 171 | 172 | followers.forEach((follower) => { 173 | if (follower.url){ 174 | bot.signAndSend({ 175 | follower: follower, 176 | message: post 177 | }, (err, data) => { 178 | 179 | }); 180 | } 181 | }); 182 | } 183 | }); 184 | 185 | if (cb){ 186 | cb(null, post); 187 | } 188 | }); 189 | }, 190 | deletePost: (postID, followerUrl, cb) => { 191 | let bot = this; 192 | // guid = crypto.randomBytes(16).toString('hex'); 193 | 194 | bot.signAndSend({ 195 | follower: { 196 | url: followerUrl 197 | }, 198 | message: { 199 | '@context': 'https://www.w3.org/ns/activitystreams', 200 | // 'summary': `${ bot} deleted a post`, 201 | // 'id': `${ bot.bot_url }/${ guid }`, 202 | 'type': 'Delete', 203 | 'actor': `${ bot.bot_url }/bot`, 204 | 'object': `${ bot.bot_url }/post/${ postID }` 205 | } 206 | }, (err, data) => { 207 | if (cb){ 208 | cb(err, data); 209 | } 210 | }); 211 | }, 212 | accept: (payload, cb) => { 213 | let bot = this, 214 | guid = crypto.randomBytes(16).toString('hex'); 215 | 216 | dbHelper.getEvent(payload.id, (err, eventData) => { 217 | console.log('getEvent', err, eventData); 218 | 219 | console.log(bot); 220 | 221 | bot.signAndSend({ 222 | follower: { 223 | url: payload.actor 224 | }, 225 | message: { 226 | '@context': 'https://www.w3.org/ns/activitystreams', 227 | 'id': `${ bot.bot_url }/${ guid }`, 228 | 'type': 'Accept', 229 | 'actor': `${ bot.bot_url }/bot`, 230 | 'object': payload, 231 | } 232 | }, (err, data) => { 233 | if (eventData){ 234 | err = 'duplicate event'; 235 | console.log('error: duplicate event'); 236 | } else { 237 | console.log('saving event', payload.id); 238 | dbHelper.saveEvent(payload.id); 239 | } 240 | 241 | if (cb){ 242 | cb(err, payload, data); 243 | } 244 | }); 245 | 246 | }); 247 | // dbHelper.getEvent(payload.id, (err, data) => { 248 | // console.log('getEvent', err, data); 249 | 250 | // if (!err && !data){ 251 | // bot.signAndSend({ 252 | // follower: { 253 | // url: payload.actor 254 | // }, 255 | // message: { 256 | // '@context': 'https://www.w3.org/ns/activitystreams', 257 | // 'id': `${ bot.bot_url }/${ guid }`, 258 | // 'type': 'Accept', 259 | // 'actor': `${ bot.bot_url }/bot`, 260 | // 'object': payload, 261 | // } 262 | // }, (err, data) => { 263 | // if (cb){ 264 | // cb(err, payload, data); 265 | // } 266 | // console.log('saving event', payload.id) 267 | // dbHelper.saveEvent(payload.id); 268 | // }); 269 | // } else if (!err){ 270 | // console.log('duplicate event'); 271 | // } 272 | // }); 273 | }, 274 | signAndSend: (options, cb) => { 275 | let bot = this; 276 | // console.log('message to sign:'); 277 | // console.log(util.inspect(options.message, false, null, true)); 278 | 279 | // options.follower.url = options.follower.url.replace('http://localhost:3000', 'https://befc66af.ngrok.io'); 280 | 281 | if (options.follower.url && options.follower.url !== 'undefined'){ 282 | options.follower.domain = url.parse(options.follower.url).hostname; 283 | 284 | let signer = crypto.createSign('sha256'), 285 | d = new Date(), 286 | stringToSign = `(request-target): post /inbox\nhost: ${ options.follower.domain}\ndate: ${ d.toUTCString() }`; 287 | 288 | signer.update(stringToSign); 289 | signer.end(); 290 | 291 | let signature = signer.sign(privateKey); 292 | let signatureB64 = signature.toString('base64'); 293 | let header = `keyId="${ botUrl }/bot",headers="(request-target) host date",signature="${ signatureB64}"`; 294 | 295 | let reqObject = { 296 | url: `https://${ options.follower.domain }/inbox`, 297 | headers: { 298 | 'Host': options.follower.domain, 299 | 'Date': d.toUTCString(), 300 | 'Signature': header 301 | }, 302 | method: 'POST', 303 | json: true, 304 | body: options.message 305 | }; 306 | 307 | // console.log('request object:'); 308 | // console.log(util.inspect(reqObject, false, null, true)); 309 | 310 | request(reqObject, (error, response) => { 311 | console.log(`sent message to ${ options.follower.url}...`); 312 | if (error) { 313 | console.log('error:', error, response); 314 | } 315 | else { 316 | console.log('response:', response.statusCode, response.statusMessage); 317 | // console.log(response); 318 | } 319 | 320 | if (cb){ 321 | cb(error, response); 322 | } 323 | }); 324 | } 325 | } 326 | }; 327 | } 328 | -------------------------------------------------------------------------------- /public/libs/bootstrap/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v4.0.0 (https://getbootstrap.com) 3 | * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,n){"use strict";function i(t,e){for(var n=0;n