├── .gitignore ├── Procfile ├── README.md ├── app.js ├── app ├── client │ ├── anonications.js │ ├── behaviors │ │ └── home │ │ │ └── a_button.js │ ├── burtle_storm.js │ ├── chat.js │ ├── cordova.js │ ├── drawing.js │ ├── emojies.js │ ├── favorite_boards.js │ ├── imgur.js │ ├── john.js │ ├── newthread.js │ ├── notif.js │ ├── post_utils.js │ ├── prelude.js │ ├── prelude.json │ ├── profanity.js │ ├── settings.js │ ├── sidebar.js │ ├── sinners.js │ ├── sticky_post.js │ ├── storage.js │ ├── summarize.js │ ├── text.js │ ├── tripcode.js │ └── voyeur.js ├── controllers │ ├── archives │ │ ├── client.js │ │ ├── server.js │ │ └── test │ │ │ └── server.js │ ├── boards │ │ ├── client.js │ │ ├── server.js │ │ └── test │ │ │ └── server.js │ ├── data │ │ ├── client.js │ │ ├── server.js │ │ └── test │ │ │ └── server.js │ ├── home │ │ ├── client.js │ │ ├── server.js │ │ └── test │ │ │ ├── client.js │ │ │ └── server.js │ ├── icons │ │ ├── client.js │ │ ├── server.js │ │ └── test │ │ │ └── server.js │ ├── posts │ │ ├── client.js │ │ ├── server.js │ │ └── test │ │ │ └── server.js │ ├── profiles │ │ ├── client.js │ │ ├── server.js │ │ └── test │ │ │ └── server.js │ ├── rss │ │ ├── server.js │ │ └── test │ │ │ └── server.js │ └── search │ │ ├── client.js │ │ ├── server.js │ │ └── test │ │ └── server.js ├── fakedata.js ├── main.js ├── models │ ├── action.js │ ├── archived_post.js │ ├── ban.js │ ├── board.js │ ├── board_claim.js │ ├── board_config.js │ ├── ip.js │ ├── link.js │ ├── model.js │ ├── post.js │ ├── trophy.js │ └── user.js ├── server │ ├── anon_pocket.js │ ├── anonications.js │ ├── board_migrations.js │ ├── board_names.js │ ├── board_utils.js │ ├── chat.js │ ├── client_api.js │ ├── hidden_boards.js │ ├── makeme_store.js │ ├── md5.js │ ├── mod.js │ ├── post_links.js │ ├── posting.js │ ├── snorkel_api.js │ ├── sponsored_content.js │ ├── tripcode.js │ └── worship_boards.js └── static │ ├── favicon.ico │ ├── fonts │ ├── glyphicons-halflings-regular.woff │ ├── webhostinghub-glyphs.eot │ ├── webhostinghub-glyphs.svg │ └── webhostinghub-glyphs.ttf │ ├── images │ ├── atob-logo-404.png │ ├── atobi.png │ ├── atobn.png │ └── bootstrap │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ ├── styles │ ├── RubberBand.css │ ├── archive.css │ ├── board.css │ ├── bootstrap │ │ ├── bootstrap-editable.css │ │ ├── bootstrap.cosmo.css │ │ ├── bootstrap.css │ │ ├── bootstrap.theme.css │ │ ├── overrides.css │ │ └── responsive.css │ ├── box.css │ ├── chat.css │ ├── definitions.less │ ├── home.css │ ├── jquery.fullPage.css │ ├── jquery.sidr.light.css │ ├── links.css │ ├── logo_and_links.css │ ├── post.css │ ├── profile.css │ ├── scrollers.css │ ├── search.css │ ├── searchbar.css │ ├── settings.css │ ├── sponsored_content.css │ └── whhg.css │ ├── templates │ ├── controllers │ │ ├── about.html.erb │ │ ├── archives.html.erb │ │ ├── boards │ │ │ ├── list.html.erb │ │ │ └── show.html.erb │ │ ├── chat.html.erb │ │ ├── data │ │ │ └── data.html.erb │ │ ├── home.html.erb │ │ ├── links.html.erb │ │ ├── mods.html.erb │ │ ├── posts │ │ │ ├── posts.html.erb │ │ │ └── show.html.erb │ │ ├── profiles │ │ │ └── profiles.html.erb │ │ ├── recent.html.erb │ │ └── search │ │ │ └── search.html.erb │ └── partials │ │ ├── boards │ │ └── shared.html.erb │ │ ├── home │ │ ├── 404.html.erb │ │ ├── board_links.html.erb │ │ ├── header.html.erb │ │ ├── icons.html.erb │ │ ├── index.html.erb │ │ ├── link.html.erb │ │ ├── recent_posts.html.erb │ │ └── rules.html.erb │ │ ├── posts │ │ └── shared.html.erb │ │ ├── profiles │ │ └── stats.html.erb │ │ ├── rss.html.erb │ │ └── shared │ │ ├── benjamin_buttons.html.erb │ │ ├── chat.html.erb │ │ ├── favorites.html.erb │ │ ├── loading.html.erb │ │ ├── logo_and_links.html.erb │ │ ├── new_post.html.erb │ │ ├── search.html.erb │ │ └── settings.html.erb │ └── vendor │ ├── RubberBand.js │ ├── Sortable.js │ ├── bootstrap.min.js │ ├── cordova │ ├── cordova.js │ ├── cordova_plugins.js │ └── plugins │ │ ├── de.appplant.cordova.plugin.background-mode │ │ └── www │ │ │ └── background-mode.js │ │ ├── de.appplant.cordova.plugin.local-notification │ │ └── www │ │ │ └── local-notification.js │ │ ├── org.apache.cordova.device │ │ └── www │ │ │ └── device.js │ │ └── org.apache.cordova.inappbrowser │ │ └── www │ │ └── inappbrowser.js │ ├── fastclick.js │ ├── hex-rgb.src.js │ ├── is_mobile.js │ ├── jquery-deparam.js │ ├── jquery.chroma-hash.js │ ├── jquery.cookie.js │ ├── jquery.deserialize.js │ ├── jquery.fullPage.js │ ├── jquery.scrollTo.js │ ├── jquery.sidr.js │ ├── jquery.tagcloud.js │ ├── jquery.textcomplete.js │ ├── jquery.timeago.js │ ├── lunicode.js │ ├── marked.js │ ├── md5.js │ ├── notify.js │ ├── plotly-latest.min.js │ ├── strtotime.js │ ├── useractions.js │ ├── useractions_stub.js │ └── velocity.js ├── components ├── board_admin_panel │ ├── board_admin_panel.css │ ├── board_admin_panel.html.erb │ ├── board_admin_panel.js │ ├── events.js │ ├── package.json │ └── test │ │ ├── client.js │ │ └── server.js ├── board_claim_panel │ ├── board_claim_panel.css │ ├── board_claim_panel.html.erb │ ├── board_claim_panel.js │ ├── events.js │ ├── package.json │ └── test │ │ ├── client.js │ │ └── server.js ├── button │ ├── button.css │ ├── button.html.erb │ ├── button.js │ ├── events.js │ ├── package.json │ └── test │ │ ├── client.js │ │ └── server.js ├── component.js ├── delete_post_modal │ ├── delete_post_modal.css │ ├── delete_post_modal.html.erb │ ├── delete_post_modal.js │ ├── events.js │ └── package.json ├── markdown_dialog │ ├── events.js │ ├── markdown_dialog.css │ ├── markdown_dialog.html.erb │ ├── markdown_dialog.js │ ├── package.json │ └── test │ │ ├── client.js │ │ └── server.js ├── post │ ├── events.js │ ├── package.json │ ├── post.css │ ├── post.html.erb │ ├── post.js │ └── test │ │ ├── client.js │ │ └── server.js ├── stats_dialog │ ├── events.js │ ├── markdown_dialog.css │ ├── package.json │ ├── stats_dialog.html.erb │ └── stats_dialog.js ├── template │ ├── events.js │ ├── package.json │ ├── template.css │ ├── template.html.erb │ ├── template.js │ └── test │ │ ├── client.js │ │ └── server.js └── upeye │ ├── events.js │ ├── package.json │ ├── test │ ├── client.js │ └── server.js │ ├── upeye.css │ ├── upeye.html.erb │ └── upeye.js ├── config ├── config.js ├── config.json ├── heroku.js ├── localhost.js ├── override.js └── users.htpasswd ├── migrations ├── 20140318213854-add_votes_to_posts.js ├── 20140318231555-add_bumped_at_to_posts.js ├── 20140320182742-add_bumped_index_to_posts.js ├── 20140320213029-add_bans_table.js ├── 20140320214332-add_ips_table.js ├── 20140410104732-add_archives_table.js ├── 20140520201926-add_links_table.js ├── 20150304120553-add_burtles_to_post.js ├── 20150416212010-add_actions_table.js ├── 20151005130358-add_board_config_table.js ├── 20151005132417-add_board_claims_table.js ├── 20151114192529-add_trophies_table.js └── 20151116104703-add_anon_id_to_trophies.js ├── package.json ├── routes.json └── scripts ├── add_user.sh ├── clean_old_posts.sh ├── create_component.sh ├── create_controller.sh ├── create_partial.sh ├── create_react_component.sh ├── garbage_collection.js ├── generate_unit_tests.sh ├── grab_all_archives.sh ├── ip_conversion.js ├── link_collection.js ├── pocket_collection.js ├── run_client_tests.sh ├── run_component_tests.sh ├── run_controller_tests.sh ├── run_tests.sh ├── run_unit_tests.sh └── setup_certificates.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | db.sqlite 3 | ab.sqlite 4 | node_modules 5 | garbage_collector.log 6 | bak 7 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node app.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | WHAT 4 | ==== 5 | 6 | atob is a realtime textboard. this is the source code for it. 7 | 8 | 9 | 10 | HOWTO 11 | ===== 12 | 13 | INSTALL ATOB 14 | ------------ 15 | 16 | make sure node / npm are installed. then run `npm install` 17 | 18 | 19 | RUNNING ATOB 20 | ------------ 21 | 22 | The first time you run atob, you need to create the database by specifying 23 | RESET=true on the command line. After the first run, you should NOT use 24 | the RESET parameter again, unless you want to delete your data. 25 | 26 | 27 | The two relevant commands are: 28 | 29 | 30 | # RUN THIS COMMAND ONCE (THE FIRST TIME) 31 | PORT=8001 RESET=1 node app 32 | 33 | # RUN THIS COMMAND ANY OTHER TIME 34 | PORT=8001 node app 35 | 36 | PRUNING ATOB 37 | ------------ 38 | 39 | run scripts/clean\_old\_posts.sh from the main directory to delete and archive 40 | old posts and remove old IPs. This should be done regularly in a cronjob 41 | 42 | SETTING UP NGINX 43 | ---------------- 44 | 45 | 46 | An example NGINX sites-available config might look like: 47 | 48 | server { 49 | listen 80; 50 | 51 | # using SSL (OPTIONAL) 52 | listen 443 ssl; 53 | ssl_certificate /home/dev/atob/config/bak/signed.crt; 54 | ssl_certificate_key /home/dev/atob/config/bak/server.key; 55 | # end SSL 56 | 57 | server_name atob.xyz; 58 | server_name atob.cc; 59 | 60 | location / { 61 | # assume the app is running on 8001 62 | proxy_pass http://127.0.0.1:8001; 63 | 64 | 65 | # web socket and proxy header stuff 66 | proxy_http_version 1.1; 67 | proxy_set_header Upgrade $http_upgrade; 68 | proxy_set_header Connection "upgrade"; 69 | proxy_redirect off; 70 | proxy_set_header Host $host; 71 | proxy_set_header X-Real-IP $remote_addr; 72 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 73 | 74 | client_max_body_size 10m; 75 | client_body_buffer_size 2k; 76 | proxy_buffers 32 4k; 77 | } 78 | } 79 | 80 | 81 | ADDING ADMIN USER 82 | ----------------- 83 | 84 | go to the atob root directory and start a new node process, then run: 85 | 86 | ``` 87 | > require("superfluous"); 88 | > User = require_app("models/user"); 89 | > User.create({"tripname" : "name", "tripcode": "tripcode" }); 90 | ``` 91 | 92 | To get the tripcode, go to your atob instance and enter your details in 93 | the settings pane, then click the "report" button on a post. That will show you 94 | the tripname/tripcode for your current user. 95 | 96 | The tripcode will be a hash, not plaintext. Do not insert a plaintext password 97 | into the DB, the tripcode should look like: "c4b20b4880b3efe567b06760e7edd8bb" 98 | 99 | 100 | To verify the user was created: 101 | 102 | ``` 103 | sqlite db.sqlite 104 | > SELECT * FROM USERS; 105 | ``` 106 | 107 | If your user is in the db, the next time you click the "report" button, you 108 | should get an admin panel instead of the normal report dialog. 109 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var server = require('superfluous'); 2 | server.run(); 3 | -------------------------------------------------------------------------------- /app/client/anonications.js: -------------------------------------------------------------------------------- 1 | 2 | var ICON_LOOKUP = { 3 | e: "icon-atob", 4 | t: "icon-keyboardalt", 5 | f: "icon-glassesalt", 6 | u: "icon-glassesalt", 7 | d: "icon-ducky", 8 | b: "icon-comedy", 9 | n: "icon-toast", 10 | s: "icon-ghost" 11 | }; 12 | 13 | function get_anonicator_for(c) { 14 | if (!c) { 15 | return ""; 16 | } 17 | 18 | var ret = ICON_LOOKUP[c[0]]; 19 | if (!ret) { 20 | c = c.replace(/:/g, ""); 21 | if (c.indexOf("_") !== -1) { // glyphicons 22 | ret = "glyphicon glyphicon-" + c.replace(/_/, ""); 23 | } else { // default icon set 24 | ret = "icon-" + c; 25 | } 26 | } 27 | return ret; 28 | } 29 | 30 | var actions = { 31 | ducky: "kited", 32 | reddit: "snooing", 33 | comedy: "ducking" 34 | 35 | }; 36 | 37 | module.exports = { 38 | check: function(targetEl, anon_id, tripcode) { 39 | var ret = false; 40 | _.each(actions, function(action, index) { 41 | if (targetEl.hasClass("icon-" + index)) { 42 | SF.socket().emit("stalking", { 43 | what: action, 44 | anon: anon_id, 45 | mytrip: tripcode 46 | }); 47 | 48 | _ET.global("anonicator", action); 49 | 50 | ret = true; 51 | } 52 | 53 | }); 54 | 55 | return ret; 56 | }, 57 | get_anonicator_for: get_anonicator_for 58 | }; 59 | -------------------------------------------------------------------------------- /app/client/behaviors/home/a_button.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | events: { 3 | "click" : "sample_click" 4 | }, 5 | 6 | sample_click: function() { 7 | $("#clickit") 8 | .html("nice job :)") 9 | .fadeIn(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/client/chat.js: -------------------------------------------------------------------------------- 1 | var HIDDEN = true; 2 | var COUNT = 0; 3 | module.exports = { 4 | add_socket_subscriptions: function(s) { 5 | s.on("new_chat", function(reply) { 6 | if (HIDDEN) { 7 | COUNT++; 8 | $(".chat .chat_header .counter").text("(" + COUNT + ")"); 9 | } 10 | 11 | if (window._POSTS.chat) { 12 | window._POSTS.chat.add_reply(reply); 13 | } 14 | }); 15 | 16 | }, 17 | controller_events: { 18 | "click .chat_header" : "toggle_chat" 19 | }, 20 | show_chat_popup: function() { 21 | _ET.local("chat", "show_popup"); 22 | this.$el.find(".chat_content").slideDown(); 23 | this.$el.find(".chat").addClass("visible"); 24 | var repliesEl = this.$el.find(".chat .replies"); 25 | if (repliesEl.length) { 26 | repliesEl.scrollTop(repliesEl[0].scrollHeight); 27 | } 28 | COUNT = 0; 29 | $(".chat .chat_header .counter").empty(); 30 | 31 | 32 | }, 33 | hide_chat_popup: function() { 34 | _ET.local("chat", "hide_popup"); 35 | this.$el.find(".chat_content").slideUp(); 36 | this.$el.find(".chat").removeClass("visible"); 37 | 38 | }, 39 | toggle_chat: function() { 40 | HIDDEN = !HIDDEN; 41 | if (HIDDEN) { 42 | this.hide_chat_popup(); 43 | } else { 44 | this.show_chat_popup(); 45 | } 46 | }, 47 | show_chat: function(post_id) { 48 | var tries = 0; 49 | function try_again() { 50 | tries++; 51 | _.delay(function() { 52 | try { 53 | window._POSTS.chat = window._POSTS[post_id]; 54 | } catch (e) { 55 | if (tries < 20) { 56 | try_again(); 57 | } 58 | } 59 | }, 300); 60 | } 61 | 62 | try_again(); 63 | 64 | // This is where we can show and hide chat? 65 | $(".chat").removeClass("hidden"); 66 | var repliesEl = $(".chat .replies"); 67 | if (repliesEl.length) { 68 | repliesEl.scrollTop(repliesEl[0].scrollHeight); 69 | } 70 | 71 | }, 72 | }; 73 | -------------------------------------------------------------------------------- /app/client/drawing.js: -------------------------------------------------------------------------------- 1 | var iframe; 2 | var insertedFrame = false; 3 | var clearFrameTimer; 4 | var showing = false; 5 | 6 | function toggler(e) { 7 | if (e.keyCode === 192 && e.ctrlKey) { 8 | module.exports.toggle(); 9 | 10 | } 11 | } 12 | 13 | toggler = _.throttle(toggler, 100); 14 | 15 | $("html").keyup(toggler); 16 | $(window).on("message", function(msg) { 17 | if (msg.originalEvent.data == "tilde") { 18 | module.exports.hide(); 19 | } 20 | }); 21 | 22 | 23 | function maybehideframe() { 24 | clearTimeout(clearFrameTimer); 25 | clearFrameTimer = setTimeout(function() { 26 | if (showing) { 27 | return; 28 | } 29 | 30 | iframe.remove(); 31 | iframe = null; 32 | insertedFrame = false; 33 | 34 | }, 30000); 35 | 36 | 37 | } 38 | 39 | function checkInstall() { 40 | if (!iframe) { 41 | module.exports.install(); 42 | } 43 | 44 | if (!insertedFrame) { 45 | $("html").append(iframe); 46 | insertedFrame = true; 47 | } 48 | 49 | } 50 | module.exports = { 51 | install: function() { 52 | if (!iframe) { 53 | 54 | iframe = $("