server.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. require('dotenv').config();
  2. var fs = require('fs');
  3. var express = require('express');
  4. var bodyParser = require('body-parser');
  5. var app = express();
  6. var http = require('http').Server(app);
  7. var ref = require('instagram-id-to-url-segment');
  8. let date = require('date-and-time');
  9. var urlSegmentToInstagramId = ref.urlSegmentToInstagramId;
  10. var Client = require('instagram-private-api').V1;
  11. var device = new Client.Device('anonbot.wl');
  12. var storage = new Client.CookieFileStorage(__dirname + '/cookies/anonbot.json');
  13. const pngToJpeg = require('png-to-jpeg');
  14. var wrap = require('word-wrap');
  15. const { createCanvas, loadImage, registerFont } = require('canvas');
  16. registerFont('./fonts/SourceCodePro-Regular.ttf', {family: 'SourceCodePro'});
  17. const canvas = createCanvas(1080, 1080);
  18. const ctx = canvas.getContext('2d');
  19. var Airtable = require('airtable');
  20. var logs = new Airtable({apiKey: process.env.AIRTABLE_API_KEY}).base('appDowHJJVQTHNJfk');
  21. var blacklist = new Airtable({apiKey: process.env.AIRTABLE_API_KEY}).base('applZHoMDx5uF9h1Z');
  22. var sha256 = require('crypto-js/sha256');
  23. function createSubmission(text, fillStyle, ip, isResponse, responseText, color) {
  24. ctx.clearRect(0, 0, canvas.width, canvas.height);
  25. var formatted = wrap(text, {indent: '', width: 28});
  26. var truncated = formatted.length > 355 ? formatted.substr(0, 356) + "\u2026" : formatted;
  27. ctx.fillStyle = fillStyle;
  28. ctx.fillRect(0, 0, 1080, 1080);
  29. ctx.font = '62px "SourceCodePro"';
  30. ctx.fillStyle = '#FFF';
  31. ctx.fillText(truncated, 17, 65);
  32. var buf = canvas.toBuffer();
  33. fs.writeFileSync("submission.png", buf);
  34. //convert to jpeg because currently api only supports jpeg
  35. let buffer = fs.readFileSync("./submission.png");
  36. pngToJpeg()(buffer)
  37. .then(output => fs.writeFile("./submission.jpeg", output, function(err) {
  38. if (err) console.log(err);
  39. fs.exists("./submission.jpeg", function(exists) {
  40. if (exists && isResponse) publish(responseText, ip, isResponse, color);
  41. else if (exists && !isResponse) publish(text, ip, isResponse, color);
  42. })
  43. }));
  44. fs.unlinkSync('./submission.png');
  45. }
  46. function createResponse(text, originalText, ip, color) {
  47. ctx.clearRect(0, 0, canvas.width, canvas.height);
  48. var formatted = wrap(text, {indent: '', width: 28});
  49. var truncated = formatted.length > 355 ? formatted.substr(0, 356) + "\u2026" : formatted;
  50. if (color === "red") ctx.fillStyle = "#b20000";
  51. else ctx.fillStyle = "#404040";
  52. //ctx.fillStyle = "#c6c6c6";
  53. ctx.fillRect(0, 0, 1080, 1080);
  54. ctx.font = '62px "SourceCodePro"';
  55. ctx.fillStyle = '#FFF';
  56. ctx.fillText(truncated, 17, 65);
  57. var buf = canvas.toBuffer();
  58. fs.writeFileSync("response.png", buf);
  59. let buffer = fs.readFileSync("./response.png");
  60. pngToJpeg()(buffer)
  61. .then(output => fs.writeFile("./response.jpeg", output, function(err) {
  62. if (err) console.log(err);
  63. fs.exists("./response.jpeg", function(exists) {
  64. if (exists) {
  65. console.log(color);
  66. if (color === "gray") createSubmission(originalText, '#b20000', ip, true, text + "\n---\n" + originalText, "red");
  67. else createSubmission(originalText, '#404040', ip, true, text + "\n---\n" + originalText, "gray");
  68. }
  69. })
  70. }));
  71. fs.unlinkSync('./response.png');
  72. }
  73. function publish(caption, ip, isResponse, color) {
  74. if (isResponse) {
  75. var photos = [
  76. {
  77. type: 'photo',
  78. size: [1000,1000],
  79. data: './response.jpeg'
  80. },
  81. {
  82. type: 'photo',
  83. size: [1000,1000],
  84. data: './submission.jpeg'
  85. }
  86. ], disabledComments = false;
  87. Client.Session.create(device, storage, 'anonbot.wl', process.env.ANON_PASSWORD)
  88. .then(function(session) {
  89. Client.Upload.album(session, photos)
  90. .then(function(payload) {
  91. console.log("Uploaded new response!");
  92. Client.Media.configureAlbum(session, payload, caption, disabledComments)
  93. })
  94. })
  95. }
  96. else {
  97. Client.Session.create(device, storage, 'anonbot.wl', process.env.ANON_PASSWORD)
  98. .then(function(session) {
  99. Client.Upload.photo(session, './submission.jpeg')
  100. .then(function(upload) {
  101. console.log("uploading...");
  102. return Client.Media.configurePhoto(session, upload.params.uploadId, caption);
  103. })
  104. .then(function(medium) {
  105. console.log("photo uploaded at " + medium.params.webLink);
  106. })
  107. });
  108. }
  109. log(caption, ip);
  110. }
  111. function postComment(id, comment) {
  112. Client.Session.create(device, storage, 'anonbot.wl', process.env.ANON_PASSWORD)
  113. .then(function(session) {
  114. console.log("posted comment " + comment);
  115. return Client.Comment.create(session, ''+id, comment);
  116. })
  117. }
  118. function postReponse(url, comment, ip) {
  119. Client.Session.create(device, storage, 'anonbot.wl', process.env.ANON_PASSWORD)
  120. .then(function(session) {
  121. return Client.Media.getByUrl(session, url)
  122. .then(function(data) {
  123. getWhichColor().then(function(color) {
  124. createResponse(comment, data._params.caption, ip, color);
  125. })
  126. })
  127. })
  128. }
  129. function delPost(id) {
  130. Client.Session.create(device, storage, 'anonbot.wl', process.env.ANON_PASSWORD)
  131. .then(function(session) {
  132. return Client.Media.delete(session, ''+id);
  133. })
  134. }
  135. function log(caption, ip, color) {
  136. var now = new Date();
  137. // set to eastern time
  138. now.setTime(now.getTime()+now.getTimezoneOffset()*60*1000);
  139. var estDate = new Date(now.getTime() + -240*60*1000);
  140. let formattedDate = date.format(estDate, 'YYYY/MM/DD HH:mm:ss');
  141. logs('Anonbot Logs').create({
  142. "Time": formattedDate,
  143. "Post": caption,
  144. "IP Hash": ""+sha256(ip)
  145. }, function(err, record) {
  146. if (err) { console.error(err); return; }
  147. console.log("new log created! " + record.getId());
  148. })
  149. }
  150. function getClientIP(req){ // Anonbot logs IPs for safety & moderation
  151. var ip = (req.headers['x-forwarded-for'] || req.connection.remoteAddress).split(',');
  152. return ip[0];
  153. }
  154. function determineIfBanned(address) {
  155. var banned = false;
  156. return new Promise(function(resolve, reject) {
  157. blacklist('Blacklist').select({
  158. view: "Grid view"
  159. }).eachPage(function page(records, fetchNextPage) {
  160. records.forEach(function(record) {
  161. if (record.get('IP Hash') === ""+sha256(address)) {
  162. console.log("A banned user tried to access the site!");
  163. banned = true;
  164. }
  165. })
  166. fetchNextPage();
  167. }, function done(err) {
  168. if (err) reject(err);
  169. resolve(banned);
  170. })
  171. })
  172. }
  173. function getWhichColor() {
  174. return new Promise(function(resolve, reject) {
  175. Client.Session.create(device, storage, 'anonbot.wl', process.env.ANON_PASSWORD)
  176. .then(function(session) {
  177. session.getAccount()
  178. .then(function(account) {
  179. if (account.params.mediaCount % 2 === 0) resolve("gray");
  180. else resolve("red");
  181. })
  182. })
  183. })
  184. }
  185. function getShortcode(url) {
  186. var parts = url.split('/');
  187. return parts[4];
  188. }
  189. app.use('/public', express.static('public'));
  190. app.use(bodyParser.urlencoded({ extended: false }));
  191. app.use(bodyParser.json());
  192. app.post("/submission", function(req, res) {
  193. console.log("received submission " + req.body.anon);
  194. if (req.body.anon === "") return res.redirect('/');
  195. getWhichColor().then(function(color) {
  196. if (color === "red") createSubmission(req.body.anon, '#b20000', getClientIP(req), false, "", "gray");
  197. else createSubmission(req.body.anon, '#404040', getClientIP(req), false, "", "red");
  198. })
  199. //createSubmission(req.body.anon, '#404040', getClientIP(req), false);
  200. return res.redirect('/submitted');
  201. });
  202. app.post("/postcomment", function(req, res) {
  203. var commentString = req.body.comment.split('::');
  204. var comment = commentString[0];
  205. var commentType = commentString[1];
  206. console.log("received comment " + comment + " type " + commentType + " on " + req.body.url);
  207. var shortcode = getShortcode(req.body.url);
  208. Client.Session.create(device, storage, 'anonbot.wl', process.env.ANON_PASSWORD)
  209. .then(function(session) {
  210. return Client.Media.getByUrl(session, ''+req.body.url)
  211. .then(function(data) {
  212. if (data._params.user.username === "anonbot.wl") {
  213. if (commentType === "comm") postComment(urlSegmentToInstagramId(shortcode), comment);
  214. else postReponse(req.body.url, comment, getClientIP(req));
  215. return res.redirect('/commented');
  216. } else {
  217. console.log("comment not posted: post is not an Anonbot post");
  218. return res.redirect('/');
  219. }
  220. })
  221. .catch(function(err) {
  222. if (err) {
  223. console.log("commment not posted: url is invalid");
  224. return res.redirect('/');
  225. }
  226. })
  227. })
  228. });
  229. app.post("/delpost", function(req, res) {
  230. console.log("received deletion request for " + req.body.link);
  231. if (req.body.key === process.env.MOD_KEY) {
  232. var shortcode = getShortcode(req.body.url);
  233. delPost(urlSegmentToInstagramId(shortcode));
  234. console.log("deletion successful");
  235. return res.redirect(req.body.url);
  236. } else {
  237. console.log("request denied: incorrect mod key");
  238. return res.redirect('/');
  239. }
  240. });
  241. app.post("/modpost", function(req, res) {
  242. console.log("received mod post request " + req.body.mod);
  243. if (req.body.key === process.env.MOD_KEY) {
  244. createSubmission(req.body.mod, '#e59d0b', false); // prev. #b20000
  245. return res.redirect('/submitted');
  246. } else {
  247. console.log("request denied: incorrect mod key");
  248. return res.redirect('/');
  249. }
  250. })
  251. app.post("/banip", function(req, res) {
  252. console.log("received ban request for IP " + req.body.ip);
  253. if (req. body.key === process.env.MOD_KEY) {
  254. blacklist('Blacklist').create({
  255. "IP": req.body.ip,
  256. "Reason": req.body.reason
  257. }, function(err) {
  258. if (err) { console.error(err); return; }
  259. console.log("banned " + req.body.ip);
  260. res.redirect('/banned');
  261. })
  262. } else {
  263. console.log("request denied: incorrect mod key");
  264. return res.redirect('/');
  265. }
  266. })
  267. app.get("/", function(request, response) {
  268. determineIfBanned(getClientIP(request)).then(function(banned) {
  269. if (banned) return response.sendFile(__dirname + '/views/banned.html');
  270. else return response.sendFile(__dirname + '/views/index.html');
  271. })
  272. });
  273. app.get("/submitted", function(request, response) {
  274. response.sendFile(__dirname + '/views/submitted.html');
  275. });
  276. app.get("/delete", function(request, response) {
  277. response.sendFile(__dirname + '/views/delete.html');
  278. });
  279. app.get("/modpost", function(request, response) {
  280. response.sendFile(__dirname + '/views/modpost.html');
  281. });
  282. app.get("/respond", function(request, response) {
  283. determineIfBanned(getClientIP(request)).then(function(banned) {
  284. if (banned) return response.sendFile(__dirname + '/views/banned.html');
  285. else return response.sendFile(__dirname + '/views/respond.html');
  286. })
  287. });
  288. app.get("/commented", function(request, response) {
  289. response.sendFile(__dirname + '/views/commented.html');
  290. });
  291. app.get("/ban", function(request, response) {
  292. response.sendFile(__dirname + '/views/ban.html');
  293. })
  294. app.get("/banned", function(request, response) {
  295. response.sendFile(__dirname + '/views/banned.html');
  296. });
  297. http.listen(3000);