diff --git a/.gitignore b/.gitignore index 562c145..6945896 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ vendor # generated js contrib/static/nntpchan.js +contrib/static/js/nntpchan.js contrib/static/miner-js.js #docs trash diff --git a/README.md b/README.md index 390de84..981de62 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ NNTPChan **NNTPChan** (previously known as overchan) is a decentralized imageboard that uses the [NNTP protocol](https://en.wikipedia.org/wiki/Network_News_Transfer_Protocol) (network-news transfer protocol) to synchronize content between many different servers. It utilizes cryptographically signed posts to perform optional/opt-in decentralized moderation. -This repository contains resources used by the core daemon which is located on [GitHub](https://github.com/majestrate/srndv2) (for now) along with general documentation, [here](doc/) +This repository contains resources used by the core daemon which is located on [GitHub](https://github.com/majestrate/srndv2) (for now) along with general documentation, [here](doc/). ##Getting started -[This](doc) is a step-by-step guide for getting up-and-running with NNTPChan as well as documentation for developers wwho want to either work on NNTPChan directly or use NNTPChan in their aplications with the API. +[This](doc) is a step-by-step guide for getting up-and-running with NNTPChan as well as documentation for developers who want to either work on NNTPChan directly or use NNTPChan in their aplications with the API. ##Bugs and issues @@ -27,6 +27,15 @@ Tor node list: 2. [chan](http://ev7fnjzjdbtu3miq.onion/) 3. [oniichan](http://sfw.oniichanylo2tsi4.onion/) +##Clients + +NNTP (confirmed working): + +* Thunderbird + +Web: + +* [Yukko](https://github.com/faissaloo/Yukko): ncurses based nntpchan web ui reader ##Support diff --git a/build-js.sh b/build-js.sh index 4fafc1c..0ffa22c 100755 --- a/build-js.sh +++ b/build-js.sh @@ -15,22 +15,11 @@ if [ ! -f "$GOPATH/bin/minify" ]; then echo "set up minifiy" go get -v github.com/tdewolff/minify/cmd/minify fi -if [ ! -f "$GOPATH/bin/gopherjs" ]; then - echo "set up gopherjs" - go get -v -u github.com/gopherjs/gopherjs -fi - -# build cuckoo miner -echo "Building cuckoo miner" -go get -v -u github.com/ZiRo-/cuckgo/miner_js -"$GOPATH/bin/gopherjs" -m -v build github.com/ZiRo-/cuckgo/miner_js -mv ./miner_js.js ./contrib/static/miner-js.js -rm ./miner_js.js.map outfile=$PWD/contrib/static/nntpchan.js lint() { - if [ "x$(which jslint)" == "x" ] ; then + if [ "$(which jslint)" == "" ] ; then # no jslint true else @@ -42,8 +31,9 @@ lint() { mini() { echo "minify $1" echo "" >> $2 - echo "/* local file: $1 */" >> $2 + echo "/* begin $1 */" >> $2 "$GOPATH/bin/minify" --mime=text/javascript >> $2 < $1 + echo "/* end $1 */" >> $2 } # do linting too @@ -54,7 +44,19 @@ if [ "x$1" == "xlint" ] ; then done fi -echo -e "//For source code and license information please check https://github.com/majestrate/nntpchan \n" > $outfile +rm -f "$outfile" + +echo '/*' >> $outfile +echo ' * For source code and license information please check https://github.com/majestrate/nntpchan' >> $outfile +brandingfile=./contrib/branding.txt +if [ -e "$brandingfile" ] ; then + echo ' *' >> $outfile + while read line; do + echo -n ' * ' >> $outfile; + echo $line >> $outfile; + done < $brandingfile; +fi +echo ' */' >> $outfile if [ -e ./contrib/js/contrib/*.js ] ; then for f in ./contrib/js/contrib/*.js ; do @@ -62,10 +64,16 @@ if [ -e ./contrib/js/contrib/*.js ] ; then done fi -mini ./contrib/js/main.js_ "$outfile" +mini ./contrib/js/entry.js "$outfile" # local js -for f in ./contrib/js/*.js ; do +for f in ./contrib/js/nntpchan/*.js ; do mini "$f" "$outfile" done + +# vendor js +for f in ./contrib/js/vendor/*.js ; do + mini "$f" "$outfile" +done + echo "ok" diff --git a/build.sh b/build.sh index bdfd02d..e82ce04 100755 --- a/build.sh +++ b/build.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash root=$(readlink -e "$(dirname "$0")") set -e -if [ "x" == "x$root" ] ; then +if [ "" == "$root" ] ; then root=$PWD/${0##*} fi cd "$root" @@ -24,9 +24,13 @@ rev="QmPAqM7anxdr1ngPmJz9J9AAxDLinDz2Eh9aAzLF9T7LNa" ipfs="no" rebuildjs="yes" _next="" +unstable="no" # check for build flags for arg in "$@" ; do case $arg in + "--unstable") + unstable="yes" + ;; "--no-js") rebuildjs="no" ;; @@ -52,13 +56,13 @@ for arg in "$@" ; do esac done -if [ "x$rev" == "x" ] ; then +if [ "$rev" == "" ] ; then echo "revision not specified" exit 1 fi cd "$root" -if [ "x$rebuildjs" == "xyes" ] ; then +if [ "$rebuildjs" == "yes" ] ; then echo "rebuilding generated js..." ./build-js.sh fi @@ -66,7 +70,7 @@ unset GOPATH export GOPATH=$PWD/go mkdir -p "$GOPATH" -if [ "x$ipfs" == "xyes" ] ; then +if [ "$ipfs" == "yes" ] ; then if [ ! -e "$GOPATH/bin/gx" ] ; then echo "obtaining gx" go get -u -v github.com/whyrusleeping/gx @@ -83,10 +87,17 @@ if [ "x$ipfs" == "xyes" ] ; then go get -d -v go build -v . mv nntpchan srndv2 + echo -e "Built\n" + echo "Now configure NNTPChan with ./srndv2 setup" else - go get -u -v github.com/majestrate/srndv2 - cp "$GOPATH/bin/srndv2" "$root" + if [ "$unstable" == "yes" ] ; then + go get -u -v github.com/majestrate/srndv2/cmd/nntpchan + cp "$GOPATH/bin/nntpchan" "$root" + echo "built unstable, if you don't know what to do, run without --unstable" + else + go get -u -v github.com/majestrate/srndv2 + cp "$GOPATH/bin/srndv2" "$root" + echo -e "Built\n" + echo "Now configure NNTPChan with ./srndv2 setup" + fi fi - -echo -e "Built\n" -echo "Now configure NNTPChan with ./srndv2 setup" diff --git a/contrib/frontends/php/vichan/post2nntp.php b/contrib/frontends/php/vichan/post2nntp.php new file mode 100644 index 0000000..cb8e9b7 --- /dev/null +++ b/contrib/frontends/php/vichan/post2nntp.php @@ -0,0 +1,89 @@ + $val) { + $val = str_replace("\n", "\n\t", $val); + $out .= "$id: $val\r\n"; + } + $out .= "\r\n"; + $out .= $content; + + return $out; +} + +function shoveitup($msg, $id) { + $s = fsockopen("tcp://localhost:1119"); + fgets($s); + fputs($s, "MODE STREAM\r\n"); + fgets($s); + fputs($s, "TAKETHIS $id\r\n"); + fputs($s, $msg); + fputs($s, "\r\n.\r\n"); + fgets($s); + fclose($s); +} + +$time = time(); + +echo "\n@@@@ Thread:\n"; +echo $m0 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.0000.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None"], +[['type' => 'text/plain', 'text' => "THIS IS A NEW TEST THREAD"]]); + +echo "\n@@@@ Single msg:\n"; +echo $m1 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.1234.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], +[['type' => 'text/plain', 'text' => "hello world, with no image :("]]); + +echo "\n@@@@ Single msg and pseudoimage:\n"; +echo $m2 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.2137.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], +[['type' => 'text/plain', 'text' => "hello world, now with an image!"], + ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"]]); + +echo "\n@@@@ Single msg and two pseudoimages:\n"; +echo $m3 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.1488.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], +[['type' => 'text/plain', 'text' => "hello world, now WITH TWO IMAGES!!!"], + ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"], + ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif2.gif"]]); + +shoveitup($m0, "<1234.0000.".$time."@example.vichan.net>"); +sleep(1); +shoveitup($m1, "<1234.1234.".$time."@example.vichan.net>"); +sleep(1); +shoveitup($m2, "<1234.2137.".$time."@example.vichan.net>"); +shoveitup($m3, "<1234.2131.".$time."@example.vichan.net>"); diff --git a/contrib/js/contrib/readme.md b/contrib/js/contrib/readme.md new file mode 100644 index 0000000..a0ea923 --- /dev/null +++ b/contrib/js/contrib/readme.md @@ -0,0 +1 @@ +3rd party javascript diff --git a/contrib/js/main.js_ b/contrib/js/entry.js similarity index 100% rename from contrib/js/main.js_ rename to contrib/js/entry.js diff --git a/contrib/js/api.js b/contrib/js/nntpchan/api.js similarity index 100% rename from contrib/js/api.js rename to contrib/js/nntpchan/api.js diff --git a/contrib/js/banner.js b/contrib/js/nntpchan/banner.js similarity index 93% rename from contrib/js/banner.js rename to contrib/js/nntpchan/banner.js index e6e9c62..19eb7f6 100644 --- a/contrib/js/banner.js +++ b/contrib/js/nntpchan/banner.js @@ -1,4 +1,4 @@ -var banner_count = 3; +var banner_count = 5; // inject a banner into an element function nntpchan_inject_banners(elem, prefix) { diff --git a/contrib/js/captcha-reload.js b/contrib/js/nntpchan/captcha-reload.js similarity index 100% rename from contrib/js/captcha-reload.js rename to contrib/js/nntpchan/captcha-reload.js diff --git a/contrib/js/expand-image.js b/contrib/js/nntpchan/expand-image.js similarity index 100% rename from contrib/js/expand-image.js rename to contrib/js/nntpchan/expand-image.js diff --git a/contrib/js/expand-video.js b/contrib/js/nntpchan/expand-video.js similarity index 100% rename from contrib/js/expand-video.js rename to contrib/js/nntpchan/expand-video.js diff --git a/contrib/js/nntpchan/hide-post.js b/contrib/js/nntpchan/hide-post.js new file mode 100644 index 0000000..bb54367 --- /dev/null +++ b/contrib/js/nntpchan/hide-post.js @@ -0,0 +1,206 @@ +/** hidepost.js -- hides posts from page given $things */ + + +function get_hidden_posts() { + var st = get_storage(); + var prefix = "nntpchan_hide_post_"; + return { + all : function() { + var msgids = []; + for ( var k in st) { + if (k.indexOf(prefix) == 0) { + var m = k.substring(prefix.length); + msgids.push(m); + } + } + return msgids; + }, + + add : function (msg) { + st[prefix+msg] = "post"; + }, + + del : function (msg) { + st.removeItem(prefix+msg); + } + } +} + + + +// is a post elem an OP? +function postIsOP(elem) { + var ds = elem.dataset; + return ds && ds.rootmsgid == ds.msgid ; +} + +function _hide_elem(elem, fade) { + if(!fade) { + if (elem.style) { + elem.style.display = "none"; + } else { + elem.style = {display: "none" }; + } + elem.dataset.userhide = "yes"; + } else { + $(elem).fadeOut(400, function() { + _hide_elem(elem); + }); + } +} + +function _unhide_elem(elem) { + $(elem).fadeIn(); + elem.dataset.userhide = "no"; +} + +// return true if element is hidden +function _elemIsHidden(elem) { + return elem.dataset && elem.dataset.userhide == "yes"; +} + +// hide a post +function hidepost(elem, nofade) { + console.log("hidepost("+elem.dataset.msgid+")"); + var posts = get_hidden_posts(); + if (posts) { + // add to persitant hide + posts.add(elem.dataset.msgidhash); + } + if(postIsOP(elem)) { + // hide thread it's an OP + var thread = document.getElementById("thread_"+elem.dataset.rootmsgidhash); + if (thread) { + var e = thread.getElementsByClassName("post"); + for ( var idx = 0; idx < e.length ; idx ++ ) { + if (e[idx].dataset.msgid == elem.dataset.msgid) continue; // don't apply + hidepost(e[idx]); + } + } + } + // hide attachments and post body + var es = elem.getElementsByClassName("attachments"); + for (var idx = 0; idx < es.length ; idx ++ ) { + _hide_elem(es[idx], !nofade); + } + es = elem.getElementsByClassName("post_body"); + for (var idx = 0; idx < es.length ; idx ++ ) { + _hide_elem(es[idx], !nofade); + } + es = elem.getElementsByClassName("postheader"); + for (var idx = 0; idx < es.length ; idx ++ ) { + _hide_elem(es[idx], !nofade); + } + elem.dataset.userhide = "yes"; + elem.setHideLabel("[show]"); +} + +// unhide a post +function unhidepost(elem) { + console.log("unhidepost("+elem.dataset.msgid+")"); + var posts = get_hidden_posts(); + if (posts) { + // remove from persiting hide + posts.del(elem.dataset.msgidhash); + } + if(postIsOP(elem)) { + var thread = document.getElementById("thread_"+elem.dataset.rootmsgidhash); + if(thread) { + var e = thread.getElementsByClassName("post"); + for ( var idx = 0; idx < e.length ; idx ++ ) { + if(e[idx].dataset.msgid == elem.dataset.msgid) continue; + unhidepost(e[idx]); + } + } + } + // unhide attachments and post body + var es = elem.getElementsByClassName("attachments"); + for (var idx = 0; idx < es.length ; idx ++ ) { + _unhide_elem(es[idx]); + } + es = elem.getElementsByClassName("post_body"); + for (var idx = 0; idx < es.length ; idx ++ ) { + _unhide_elem(es[idx]); + } + es = elem.getElementsByClassName("postheader"); + for (var idx = 0; idx < es.length ; idx ++ ) { + _unhide_elem(es[idx]); + } + + elem.dataset.userhide = "no"; + elem.setHideLabel("[hide]"); +} + +// hide a post given a callback that checks each post +function hideposts(check_func) { + var es = document.getElementsByClassName("post"); + for ( var idx = 0; idx < es.length ; idx ++ ) { + var elem = es[idx]; + if(check_func && elem && check_func(elem)) { + hidepost(elem); + } + } +} + +// unhide all posts given callback +// if callback is null unhide all +function unhideall(check_func) { + var es = document.getElementsByClassName("post"); + for (var idx=0 ; idx < es.length; idx ++ ) { + var elem = es[idx]; + if(!check_func) { unhide(elem); } + else if(check_func(elem)) { unhide(elem); } + } +} + +// inject posthide into page +onready(function() { + var posts = document.getElementsByClassName("post"); + for (var idx = 0 ; idx < posts.length; idx++ ) { + console.log("inject hide: "+posts[idx].dataset.msgid); + var inject = function (elem) { + var hider = document.createElement("a"); + hider.setAttribute("class", "hider"); + elem.setHideLabel = function (txt) { + var e_hider = hider; + e_hider.innerHTML = txt; + } + elem.hidepost = function() { + var e_self = elem; + var e_hider = hider; + hidepost(e_self); + } + elem.unhidepost = function() { + var e_self = elem; + var e_hider = hider; + unhidepost(e_self); + } + elem.isHiding = function() { + var e_self = elem; + return _elemIsHidden(e_self); + } + hider.appendChild(document.createTextNode("[hide]")); + hider.onclick = function() { + var e_self = elem; + if(e_self.isHiding()) { + e_self.unhidepost(); + } else { + e_self.hidepost(); + } + } + elem.appendChild(hider); + }; + inject(posts[idx]); + } + // apply persiting hidden posts + var posts = get_hidden_posts(); + if(posts) { + var all = posts.all(); + for ( var idx = 0 ; idx < all.length; idx ++ ) { + var id = all[idx]; + var elem = document.getElementById(id); + if(elem) + hidepost(elem, true); + } + } +}); diff --git a/contrib/js/nntpchan/livechan.js b/contrib/js/nntpchan/livechan.js new file mode 100644 index 0000000..9ca3096 --- /dev/null +++ b/contrib/js/nntpchan/livechan.js @@ -0,0 +1,1432 @@ + + +function buildCaptcha(domElem, prefix) { + var captcha_widget = document.createElement("div"); + + captcha_widget.className = "livechan_captcha_inner"; + + var outer = document.createElement("div"); + + outer.className = "livechan_captcha"; + + var text = document.createElement("div"); + text.textContent = "solve the captcha"; + captcha_widget.appendChild(text); + + var captcha_image = document.createElement("img"); + captcha_image.className = "livechan_captcha_image"; + var div = document.createElement("div"); + div.appendChild(captcha_image); + captcha_widget.appendChild(div); + + var captcha_entry = document.createElement("input"); + captcha_entry.className = "livechan_captcha_input"; + var div = document.createElement("div"); + div.appendChild(captcha_entry); + captcha_widget.appendChild(div); + + var captcha_submit = document.createElement("input"); + captcha_submit.setAttribute("type", "button"); + captcha_submit.value = "solve"; + var div = document.createElement("div"); + div.appendChild(captcha_submit); + captcha_widget.appendChild(div); + + outer.appendChild(captcha_widget); + domElem.appendChild(outer); + + return { + widget: outer, + button: captcha_submit, + image: captcha_image, + entry: captcha_entry, + prefix: prefix, + } +} + +function Captcha(domElem, options, callback) { + if (options) { + this.options = options; + } else { + this.options = {}; + } + + this.prefix = options.prefix || "/"; + this.widget = buildCaptcha(domElem, this.prefix); + var self = this; + this.widget.button.addEventListener("click", function() { self.process(callback); }); +} + +Captcha.prototype.load = function() { + var self = this; + var xhr = new XMLHttpRequest(); + + var url = location.protocol + "//" + location.host + this.prefix ; + + xhr.open('get', url +"captcha/new"); + xhr.onreadystatechange = function () { + if (xhr.readyState == 4 && xhr.status == 200) { + var jdata = JSON.parse(xhr.responseText); + if ( jdata ) { + self.setCaptchaId(jdata); + } + } + } + + xhr.send(); +} + +/** + * @brief set captcha id + */ +Captcha.prototype.setCaptchaId = function(data) { + this.captcha_id = data.id; + this.setImageUrl(data.url); + this.show(); +} + +Captcha.prototype.setImageUrl = function(url) { + this.widget.image.setAttribute("src", url); +} + +/** + * @brief process captcha form + */ +Captcha.prototype.process = function(callback) { + console.log("process"); + console.log(this); + if (this.captcha_id) { + var solution = this.widget.entry.value; + var self = this; + callback(this.captcha_id, solution , function(solved) { + if (solved) { + // woot we solved it + self.hide(); + } else { + // TODO: inform user of bad solution + self.load(); + } + }); + } else { + // TODO: inform user of no captcha entred + self.load(); + } +} + +/** + * @brief show the captcha pane + */ +Captcha.prototype.show = function () { + console.log("show captcha"); + var widget = this.widget.widget; + if ( widget.style ) { + widget.style.zIndex = 5; + widget.style.visibility = "visible"; + } else { + widget.style = {zIndex: 5, visibility: "visible"}; + } +} +/** + * @brief hide the captcha pane + */ +Captcha.prototype.hide = function () { + console.log("hide captcha"); + var widget = this.widget.widget; + if ( widget.style ) { + widget.style.zIndex = -1; + widget.style.visibility = "hidden"; + } else { + widget.style = {zIndex: -1, visibility: "hidden"}; + } +} + +/** + * build login widget + * for captcha / mod login + */ +function buildLogin(domElem) { + var widget = document.createElement("div"); + widget.className = "livechan_login_widget"; + widget.style.zIndex = -1; + + + var mod_div = document.createElement("div"); + mod_div.className = "livechan_login"; + + var mod_form = document.createElement("form"); + + var mod_username = document.createElement("input"); + mod_form.appendChild(mod_username); + mod_username.className = "livechan_login_username"; + + var mod_password = document.createElement("input"); + mod_password.className = "livechan_login_password"; + mod_password.setAttribute("type", "password"); + mod_form.appendChild(mod_password); + + var mod_submit = document.createElement("input"); + mod_password.className = "livechan_login_submit"; + mod_submit.setAttribute("type", "submit"); + mod_submit.setAttribute("value", "login"); + mod_form.appendChild(mod_submit); + mod_div.appendChild(mod_form); + widget.appendChild(mod_div); + domElem.appendChild(widget); + return { + widget: widget, + mod: { + form: mod_form, + username: mod_username, + password: mod_password, + submit: mod_submit + } + } +} + + +function Login(domElem) { + this._login = buildLogin(domElem); +} + +Login.prototype.show = function() { + var self = this; + self._login.widget.style.zIndex = 5; + console.log("show login widget"); +} + + + +/* + * @brief build livechan navbar + * @param domElem the root element to put the navbar in + */ +function LivechanNavbar(domElem) { + + this.navbar = document.createElement("div"); + this.navbar.className = 'livechan_navbar'; + + var container = document.createElement("div"); + // channel name label + var channelLabel = document.createElement("span"); + channelLabel.className = 'livechan_navbar_channel_label'; + + this.channel = channelLabel; + + // mod indicator + this.mod = document.createElement("span"); + this.mod.className = 'livechan_navbar_mod_indicator_inactive'; + + // TODO: don't hardcode + this.mod.textContent = "Anon"; + + // usercounter + this.status = document.createElement("span"); + this.status.className = 'livechan_navbar_status'; + + container.appendChild(this.mod); + container.appendChild(this.channel); + container.appendChild(this.status); + + this.navbar.appendChild(container); + + domElem.appendChild(this.navbar); + +} + + +/* @brief called when there is an "event" for the navbar */ +LivechanNavbar.prototype.onLivechanEvent = function (evstr) { + if ( evstr === "login:mod" ) { + // set indicator + this.mod.className = "livechan_mod_indicator_active"; + this.mod.textContent = "Moderator"; + } else if ( evstr === "login:admin" ) { + this.mod.className = "livechan_mod_indicator_admin"; + this.mod.textContent = "Admin"; + } +} + +/* @brief called when there is a notification for us */ +LivechanNavbar.prototype.onLivechanNotify = function(evstr) { + // do nothing for now + // maybe have some indicator that shows number of messages unread? +} + +/* @brief update online user counter */ +LivechanNavbar.prototype.updateUsers = function(count) { + this.updateStatus("Online: "+count); +} + +/* @brief update status label */ +LivechanNavbar.prototype.updateStatus = function(str) { + this.status.textContent = str; +} + +/* @brief set channel name */ +LivechanNavbar.prototype.setChannel = function(str) { + this.channel.textContent = str; +} + +var modCommands = [ + // login command + [/l(login)? (.*)/, function(m) { + var chat = this; + // mod login + chat.modLogin(m[2]); + }, + "login as user", "/l user:password", + ], + [/cp (\d+)/, function(m) { + var chat = this; + // permaban the fucker + chat.modAction(3, 4, m[1], "CP", -1); + }, + "handle illegal content", "/cp postnum", + ], + [/cnuke (\d+) (.*)/, function(m) { + var chat = this; + // channel ban + nuke files + chat.modAction(2, 4, m[1], m[2], -1); + }, + "channel level ban+nuke", "/cnuke postnum reason goes here", + ], + [/purge (\d+) (.*)/, function(m) { + var chat = this; + // channel ban + nuke files + chat.modAction(2, 9, m[1], m[2], -1); + }, + "channel level ban+nuke", "/cnuke postnum reason goes here", + ], + [/gnuke (\d+) (.*)/, function(m) { + var chat = this; + // global ban + nuke with reason + chat.modAction(3, 4, m[1], m[2], -1); + }, + "global ban+nuke", "/gnuke postnum reason goes here", + ], + [/gban (\d+) (.*)/, function(m) { + var chat = this; + // global ban with reason + chat.modAction(3, 3, m[1], m[2], -1); + }, + "global ban (no nuke)", "/gban postnum reason goes here", + ], + [/cban (\d+) (.*)/, function(m) { + var chat = this; + // channel ban with reason + chat.modAction(2, 3, m[1], m[2], -1); + }, + "channel level ban (no nuke)", "/cban postnum reason goes here", + ], + [/dpost (\d+)/, function(m) { + var chat = this; + // channel level delete post + chat.modAction(1, 2, m[1]); + }, + "delete post and file", "/dpost postnum", + ], + [/dfile (\d+)/, function(m) { + var chat = this; + // channel level delete file + chat.modAction(1, 1, m[1]); + }, + "delete just file", "/dpost postnum", + ] +] + + +/* + * @brief Build Notification widget + * @param domElem root element to put widget in + */ +function buildNotifyPane(domElem) { + var pane = document.createElement("div"); + pane.className = "livechan_notify_pane"; + domElem.appendChild(pane); + return pane; +} + +/* + * @brief Livechan Notification system + * @param domElem root element to put Notification Pane in. + */ +function LivechanNotify(domElem) { + this.pane = buildNotifyPane(domElem); +} + +/* @brief inform the user with a message */ +LivechanNotify.prototype.inform = function(str) { + // new Notify("livechan", {body: str}).show(); + var elem = document.createElement("div"); + elem.className = "livechan_notify_node"; + elem.textContent = Date.now() + ": " + str; + this.pane.appendChild(elem); + this.rollover(); +} + + +/* @brief roll over old messages */ +LivechanNotify.prototype.rollover = function() { + while ( this.pane.childNodes.length > this.scrollback ) { + this.pane.childNodes.removeChild(this.pane.childNodes[0]); + } +} + + + + +/* @brief Creates a structure of html elements for the + * chat. + * + * @param domElem The element to be populated with the + * chat structure. + * @param chatName The name of this chat + * @return An object of references to the structure + * created. + */ +function buildChat(chat, domElem, channel) { + channel = channel.toLowerCase(); + // build the navbar + // see nav.js + var navbar = new LivechanNavbar(domElem); + + // build the notification system + // see notify.js + var notify = new LivechanNotify(domElem); + + var output = document.createElement('div'); + output.className = 'livechan_chat_output'; + + var input_left = document.createElement('div'); + input_left.className = 'livechan_chat_input_left'; + + var input = document.createElement('form'); + input.className = 'livechan_chat_input'; + + var name = document.createElement('input'); + name.className = 'livechan_chat_input_name'; + name.setAttribute('placeholder', 'Anonymous'); + + var file = document.createElement('input'); + file.className = 'livechan_chat_input_file'; + file.setAttribute('type', 'file'); + file.setAttribute('value', 'upload'); + file.setAttribute('id', channel+'_input_file'); + + + var messageDiv = document.createElement('div'); + messageDiv.className = 'livechan_chat_input_message_div'; + + var message = document.createElement('textarea'); + message.className = 'livechan_chat_input_message'; + + var submit = document.createElement('input'); + submit.className = 'livechan_chat_input_submit'; + submit.setAttribute('type', 'submit'); + submit.setAttribute('value', 'send'); + var convobar = new ConvoBar(chat, domElem); + input_left.appendChild(name); + input_left.appendChild(convobar.subject); + input_left.appendChild(file); + input.appendChild(input_left); + messageDiv.appendChild(message); + input.appendChild(messageDiv); + input.appendChild(submit); + domElem.appendChild(output); + domElem.appendChild(input); + + return { + convobar : convobar, + notify: notify, + navbar: navbar, + output: output, + input: { + subject: convobar.subject, + form: input, + message: message, + name: name, + submit: submit, + file: file + } + }; +} + +function Connection(chat, ws, url) { + this.ws = ws; + this.chat = chat; + this.url = url; + this.ws.onmessage = function(ev) { + chat.handleData(JSON.parse(ev.data)); + } + this.ws.onclose = function() { + self.ws = null; + } + var self = this; + setInterval(function() { + if (self.ws == null) { + // try reconnecting + initWebSocket(self.chat, self.url, self); + } + }, 5000); +} + +Connection.prototype.ban = function(reason) { + if (this.ws) { + this.ws.close(); + this.ws.close = null; + alert("You have been banned for the following reason: "+reason); + } +} + +Connection.prototype.send = function(obj) { + /* Jsonify the object and send as string. */ + this.sendBinary(JSON.stringify(obj)); +} + +Connection.prototype.sendBinary = function(obj) { + if (this.ws) { + this.ws.send(obj); + } +} + +Connection.prototype.onclose = function(callback) { + if(this.ws) + this.ws.onclose = callback; +} + +/* @brief Initializes the websocket connection. + * + * @param channel The channel to open a connection to. + * @return A connection the the websocket. + */ +function initWebSocket(chat, url, connection) { + var ws = null; + if (window['WebSocket']) { + try { + ws = new WebSocket(url); + } catch (ex) { + ws = null; + } + } + if (ws) { + ws.onclose = function() { + if (connection) { + connection.ws = null; + } + }; + if (connection) { + connection.ws = ws; + connection.ws.onmessage = function(ev) { + chat.handleData(JSON.parse(ev.data)); + } + chat.clear(); + chat.handleMessage({Type: "post", PostMessage: "reconnecting..."}); + return connection; + } else { + return new Connection(chat, ws, url); + } + } +} + +/* @brief Parses and returns a message div. + * + * @param data The message data to be parsed. + * @return A dom element containing the message. + */ +function parse(text, rules, end_tag) { + var output = document.createElement('div'); + var position = 0; + var end_matched = false; + if (end_tag) { + var end_handler = function(m) { + end_matched = true; + } + rules = [[end_tag, end_handler]].concat(rules); + } + do { + var match = null; + var match_pos = text.length; + var handler = null; + for (var i = 0; i < rules.length; i++) { + rules[i][0].lastIndex = position; + var result = rules[i][0].exec(text); + if (result !== null && position <= result.index && result.index < match_pos) { + match = result; + match_pos = result.index; + handler = rules[i][1]; + } + } + var unmatched_text = text.substring(position, match_pos); + output.appendChild(document.createTextNode(unmatched_text)); + position = match_pos; + if (match !== null) { + position += match[0].length; + output.appendChild(handler(match)); + } + } while (match !== null && !end_matched); + return output; +} + +var messageRules = [ + [/>>([0-9a-f]+)/g, function(m) { + var out = document.createElement('span'); + out.className = 'livechan_internallink'; + out.addEventListener('click', function() { + var selected = document.getElementById('livechan_chat_'+m[1]); + console.log(selected.convo); + selected.select(); + selected.scrollIntoView(true); + // TODO: highlight + }); + out.appendChild(document.createTextNode('>>'+m[1])); + return out; + }], + [/^>.+/mg, function(m) { + var out = document.createElement('span'); + out.className = 'livechan_greentext'; + out.appendChild(document.createTextNode(m)); + return out; + }], + [/\[code\]\n?([\s\S]+)\[\/code\]/g, function(m) { + var out; + if (m.length >= 2 && m[1].trim !== '') { + out = document.createElement('pre'); + out.textContent = m[1]; + } else { + out = document.createTextNode(m); + } + return out; + }], + [/\[b\]\n?([\s\S]+)\[\/b\]/g, function(m) { + var out; + if (m.length >= 2 && m[1].trim !== '') { + out = document.createElement('span'); + out.className = 'livechan_boldtext'; + out.textContent = m[1]; + } else { + out = document.createTextNode(m); + } + return out; + }], + [/\[spoiler\]\n?([\s\S]+)\[\/spoiler\]/g, function(m) { + var out; + if ( m.length >= 2 && m[1].trim !== '') { + out = document.createElement('span'); + out.className = 'livechan_spoiler'; + out.textContent = m[1]; + } else { + out = document.createTextNode(m); + } + return out; + }], + [/\r?\n/g, function(m) { + return document.createElement('br'); + }], + [/==(.*)==/g, function(m) { + var out; + out = document.createElement("span"); + out.className = "livechan_redtext"; + out.textContent = m[1]; + return out; + }], + [/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/g, function(m) { + var out = document.createElement("a"); + out.href = m[1]; + out.textContent = m[1]; + return out; + }], +] + +/* @brief build the convorsation bar's elements +*/ +function buildConvoBar(domElem) { + var elem = document.createElement("div"); + elem.className = "livechan_convobar_root"; + + var subject = document.createElement("input"); + subject.className = "livechan_chat_input_convo"; + elem.appendChild(subject); + domElem.appendChild(elem); + + return { + subject: subject, + widget: elem, + } +} + +/* @brief create the chat's convorsation bar + * @param domElem the element to place everything in + */ +function ConvoBar(chat, domElem) { + this.parent = chat; + this.holder = {}; + this.domElem = domElem; + var convo = buildConvoBar(domElem); + this.widget = convo.widget; + this.subject = convo.subject; + this.active = null; + this._num = 0; +} + +ConvoBar.prototype.count = function() { + return this._num; +} + +/* @brief remove convorstation given root message-id */ +ConvoBar.prototype.removeConvo = function(msgid) { + var self = this; + console.log("remove "+msgid); + var c = self.holder[msgid]; + if (c) { + var e = document.getElementById("livechan_convobar_item_"+c.id); + if (e) e.remove(); + for(var idx = 0; idx < c.posts.length; idx ++ ) { + var id = "livechan_chat_"+c.posts[idx].HashShort; + var child = document.getElementById(id); + if(child) child.remove(); + } + + delete self.holder[msgid]; + self._num -- ; + } +} + +/* @brief update the convo bar + * @param convoId the name of this covnorsattion + */ +ConvoBar.prototype.update = function(msgid, data) { + var self = this; + if ( ! self.holder[msgid]) { + // new convo + // register convo + self.registerConvo(msgid, data); + } + // bump existing convo + var convoId = self.holder[msgid].id; + var convoElem = document.getElementById("livechan_convobar_item_"+convoId); + var convoParent = convoElem.parentElement; + if ( convoParent.children.length > 1 ) { + convoParent.removeChild(convoElem); + convoParent.insertBefore(convoElem, convoParent.childNodes[0]); + } + // add post to convo + self.holder[msgid].posts.push(data); + // do roll over + var scrollback = self.parent.options.scrollback || 30; + while(self.holder[msgid].posts.length > scrollback) { + // remove oldest from convo tracker + var child_data = self.holder[msgid].posts.shift(); + var child = document.getElementById("livechan_chat_"+child_data.ShortHash); + if(child) { + child.remove(); + } + } + // lol quartic time + while(self.count() > 10 ) { + var minid = -1; + var minmsgid = null; + for( var i in self.holder ) { + if (minid == -1 || self.holder[i].id < minid) { + minid = self.holder[i].id; + minmsgid = i; + } + } + if(minmsgid) + self.removeConvo(minmsgid); + } +} + + + +/** @brief register a new convorsation + */ +ConvoBar.prototype.registerConvo = function(msgid, data) { + var self = this; + var max_id = 0; + // get the highest convo id + for ( c in self.holder ) { + var id = self.holder[c].id; + if (id > max_id ) { + max_id = id + } + } + + self.holder[msgid] = { + subject: data.PostSubject, + msgid: data.Message_id, + id: max_id + 1, + posts: [], + group: data.Newsgroup, + ShortHash: data.ShortHash, + select: function() { + console.log("selected convo "+msgid); + if ( self.active !== msgid ) { + self.show(msgid); + } + }, + } + // make a new entry in the convo bar + var elem = document.createElement("div"); + elem.className = "livechan_convobar_item"; + elem.setAttribute("id", "livechan_convobar_item_"+ self.holder[msgid].id); + var link = document.createElement("span"); + elem.addEventListener("click", function() { self.show(msgid); }); + link.appendChild(document.createTextNode(data.PostSubject)); + elem.appendChild(link); + // prepend the element + if (self.widget.children.length > 0 ) { + self.widget.insertBefore(elem, self.widget.childNodes[0]); + } else { + self.widget.appendChild(elem); + } + self._num ++; +} + +/* @brief Only Show chats from a convorsation + */ +ConvoBar.prototype.show = function(msgid) { + var self = this; + var sheet = null; + for(var idx = 0; idx < document.styleSheets.length; idx++ ) { + var s = document.styleSheets[idx]; + if (s.ownerNode && s.ownerNode.id === "convo_filter") { + sheet = s; + break; + } + } + var rules = null; + // delete all filtering rules + if (sheet.rules) { + rules = sheet.rules; + } else { + rules = sheet.cssRules; + } + while (rules.length > 0 ) { + if (sheet.deleteRule) { + sheet.deleteRule(0); + } else if (sheet.removeRule) { + sheet.removeRule(0); + } else { + break; + } + } + + if (msgid === self.active) { + // this is resetting the view + if (sheet.insertRule) { // firefox + sheet.insertRule(".livechan_chat_output_chat { display: block; }", 0); + } else if (sheet.addRule) { // not firefox + sheet.addRule(".livechan_chat_output_chat", "display: block"); + } + // unset active highlight + var convoId = self.holder[self.active].id; + var itemElem = document.getElementById("livechan_convobar_item_"+convoId); + itemElem.style.background = null; + self.active = null; + } else { + // unset active highlight if it's there + if (self.active) { + var convoId = self.holder[self.active].id; + var itemElem = document.getElementById("livechan_convobar_item_"+convoId); + itemElem.style.background = null; + } + // set active highlight to new element + convoId = self.holder[msgid].id; + itemElem = document.getElementById("livechan_convobar_item_"+convoId); + itemElem.style.background = "red"; + var elemClass = ".livechan_chat_convo_" + convoId; + if (sheet.insertRule) { // firefox + sheet.insertRule(elemClass+ " { display: block; }", 0); + sheet.insertRule(".livechan_chat_output_chat { display: none; }", 0); + } else if (sheet.addRule) { // not firefox + sheet.addRule(".livechan_chat_output_chat", "display: none"); + sheet.addRule(elemClass, "display: block"); + } + // this convo is now active + self.active = msgid; + } + // scroll view + self.parent.scroll(); +} + +/* @brief Creates a chat. + * + * @param domElem The element to populate with chat + * output div and input form. + * @param channel The channel to bind the chat to. + * + * @param options Channel Specific options + */ +function Chat(domElem, channel, options) { + var self = this; + this.pph = 0; + this.name = channel.toLowerCase(); + this.domElem = domElem; + this.lastOp = null; + if (options) { + this.options = options; + } else { + this.options = {}; + } + + + this.chatElems = buildChat(this, this.domElem, this.name); + this.prefix = this.options.prefix || "/"; + var scheme = "wss://"; + if (location.protocol == "http:") scheme = "ws://"; + var url = scheme + location.host + this.prefix + "live?"+ this.name; + this.connection = initWebSocket(this, url); + this.initOutput(); + this.initInput(); + // set navbar channel name + this.chatElems.navbar.setChannel(this.name); + // create captcha + this.captcha = new Captcha(this.domElem, this.options, function(id, solution, callback) { + // send captcha solution + var ajax = new XMLHttpRequest(); + ajax.open("POST", self.prefix+"livechan/api/captcha"); + ajax.onreadystatechange = function() { + if(ajax.readyState == 4) { + var result = JSON.parse(ajax.responseText); + callback(result && result.success); + } + } + ajax.send(JSON.stringify({ID: id, Solution: solution})); + }); + this.captcha.hide(); + setInterval(function() { + self.tickPPHCount(); + self.tickUserCount(); + }, 5000); +} + +Chat.prototype.clear = function () { + for ( var convo in this.chatElems.convobar.holder ) { + this.chatElems.convobar.removeConvo(convo); + } +} + +/** + * @brief begin login sequence + */ +Chat.prototype.login = function() { + this.captcha.load(); +} + +/** + * @brief do mod login + */ +Chat.prototype.modLogin = function(str) { + var self = this; + self.connection.send({ModLogin: str}); +} + +Chat.prototype.modAction = function(scope, action, postID, reason, expire) { + var self = this; + self.connection.send({ + ModReason: reason, + ModScope: parseInt(scope), + ModAction: parseInt(action), + ModPostID: parseInt(postID), + ModExpire: parseInt(expire), + }); +} + +/* @brief called when our post got mentioned + * + * @param event the event that has this mention + */ +Chat.prototype.Mentioned = function(event, chat) { + var self = this; + self.notify("mentioned: "+chat); +} + +Chat.prototype.onNotifyShow = function () { + +} + + +Chat.prototype.readImage = function (elem, callback) { + var self = this; + + if (elem.files.length > 0 ) { + var reader = new FileReader(); + var file = elem.files[0]; + reader.onloadend = function(ev) { + if (ev.target.readyState == FileReader.DONE) { + callback(window.btoa(ev.target.result), file.name, file.type); + } + } + reader.readAsBinaryString(file); + } else { + callback(null, null, null); + } +} + +/* @brief Sends the message in the form. + * + * @param event The event causing a message to be sent. + */ +Chat.prototype.sendInput = function(event) { + var inputElem = this.chatElems.input; + var connection = this.connection; + var self = this; + + if (inputElem.message.value[0] == '/') { + var inp = inputElem.message.value; + var helpRegex = /(help)? (.*)/; + var helpMatch = helpRegex.exec(inp.slice(1)); + if (helpMatch) { + + } + if ( self.options.customCommands ) { + for (var i in self.options.customCommands) { + var regexPair = self.options.customCommands[i]; + var match = regexPair[0].exec(inp.slice(1)); + if (match) { + (regexPair[1]).call(self, match); + inputElem.message.value = ''; + } + } + } + // modCommands is defined in mod.js + for ( var i in modCommands ) { + var command = modCommands[i]; + var match = command[0].exec(inp.slice(1)); + if (match) { + (command[1]).call(self, match); + // don't clear input for mod command + } + } + event.preventDefault(); + return false; + } + if (inputElem.submit.disabled == false) { + var message = inputElem.message.value; + var name = inputElem.name.value; + var convo = self.chatElems.convobar.active; + var board; + if(convo) + board = self.chatElems.convobar.holder[convo].group; + if (!board) board = "overchan.live"; + console.log(board); + var subject = self.chatElems.input.subject.value; + var ajax = new XMLHttpRequest(); + ajax.open("POST", self.prefix+"livechan/api/post?newsgroup="+board, true); + ajax.onreadystatechange = function() { + if (ajax.readyState == 4) { + console.log("post done"); + // responded + var jdata = JSON.parse(ajax.responseText); + if(!jdata) { + // error parsing + console.log("parse error: data="+ajax.responseText); + } else if(jdata.captcha) { + // we need to fill out captcha + self.login(); + } else if (jdata.message_id) { + // we posted + console.log("post success:" +jdata.message_id); + if (!convo) { + self.lastOp = jdata.message_id; + } + // reset shit + inputElem.file.value = ""; + inputElem.message.value = ''; + } else if (jdata.error) { + console.log(jdata.error); + } + } else if (ajax.readyState == 3 ) { + // processing + console.log("post processing"); + } else if (ajax.readyState == 2 ) { + // sent + console.log("post sent"); + } + } + var data = new FormData(); + data.append("name", name); + data.append("subject", subject); + data.append("message", message); + if (convo) + data.append("reference", convo); + if (inputElem.file.files[0]) + data.append("attachment_0", inputElem.file.files[0]); + ajax.send(data); + /** + self.readImage(inputElem.file, function(fdata, fname, ftype) { + if (fdata) { + connection.send({Type: "post", Post: { + message: message, + subject: subject, + name: name, + reference: convo, + newsgroup: board, + files: [{name: fname, data: fdata, type: ftype}], + }}); + } else { + connection.send({Type: "post", Post: { + subject: subject, + message: message, + reference: convo, + name: name, + newsgroup: board, + }}); + } + inputElem.file.value = ""; + inputElem.message.value = ''; + }); + */ + inputElem.submit.disabled = true; + var i = parseInt(self.options.cooldown); + // fallback + if ( i == NaN ) { i = 4; } + inputElem.submit.setAttribute('value', i); + var countDown = setInterval(function(){ + inputElem.submit.setAttribute('value', --i); + }, 1000); + setTimeout(function(){ + clearInterval(countDown); + inputElem.submit.disabled = false; + inputElem.submit.setAttribute('value', 'send'); + }, i * 1000); + event.preventDefault(); + return false; + } +} + +/* @brief Binds the form submission to websockets. + */ +Chat.prototype.initInput = function() { + var inputElem = this.chatElems.input; + var connection = this.connection; + var self = this; + inputElem.form.addEventListener('submit', function(event) { + self.sendInput(event); + }); + + inputElem.message.addEventListener('keydown', function(event) { + /* If enter key. */ + if (event.keyCode === 13 && !event.shiftKey) { + self.sendInput(event); + } + }); + inputElem.message.focus(); +} + + +/* @brief show a notification to the user */ +Chat.prototype.notify = function(message) { + // show notification pane + this.showNotifyPane(); + + var notifyPane = this.chatElems.notify; + + notifyPane.inform(message); +} + +/* @brief show the notification pane */ +Chat.prototype.showNotifyPane = function () { + var pane = this.chatElems.notify.pane; + pane.style.zIndex = 5; + pane.style.visibility = 'visible'; +} + +/* @brief hide the notification pane */ +Chat.prototype.showNotifyPane = function () { + var pane = this.chatElems.notify.pane; + pane.style.zIndex = -1; + pane.style.visibility = 'hidden'; +} + +Chat.prototype.error = function(message) { + var self = this; + console.log("error: "+message); + self.notify("an error has occured: "+message); +} + +/** handle inbound websocket message */ +Chat.prototype.handleMessage = function (data) { + var self = this; + + var mtype = data.Type.toLowerCase(); + + if (mtype == "captcha" ) { + if (self.captcha_callback) { + // captcha reply + self.captcha_callback(data.Success); + // reset + self.captcha_callback = null; + } else { + // captcha challenge + self.login(); + } + } else if (mtype == "post" ) { + self.insertChat(self.generateChat(data), data); + } else if (mtype == "count" ) { + self.chatElems.navbar.updateUsers(data.UserCount); + } else if (mtype == "ban" ) { + self.connection.ban(data.Reason); + } else if (mtype == "error") { + self.insertChat(self.generateChat({PostMessage: data.Error, PostSubject: "Server Error", PostName: "Server"})) + console.log("server error: "+data.Error); + } else { + console.log("unknown message type "+mtype); + } +} + + +Chat.prototype.handleData = function(data) { + var self = this; + if( Object.prototype.toString.call(data) === '[object Array]' ) { + for (var i = 0; i < data.length; i++) { + self.handleMessage(data[i]); + } + } else { + self.handleMessage(data); + } +} + +/* @brief Binds messages to be displayed to the output. + */ +Chat.prototype.initOutput = function() { + var outputElem = this.chatElems.output; + var connection = this.connection; + var self = this; +} + +Chat.prototype.tickUserCount = function () { + var self=this; + var ajax = new XMLHttpRequest(); + ajax.open("GET", self.prefix + "livechan/api/online"); + ajax.onreadystatechange = function () { + if (ajax.readyState == 4 && ajax.status == 200 ) { + var data = JSON.parse(ajax.responseText); + if (data && data.online) { + self.updateUserStats(data.online); + } + } + } + ajax.send(); +} + +Chat.prototype.tickPPHCount = function () { + var self=this; + var convo = self.chatElems.convobar.active; + var board; + if(convo) + board = self.chatElems.convobar.holder[convo].group; + if(!board) { + // no board selected? + var h = document.location.hash; + if (h.length > 1 ) { + board = "overchan." + h.substr(1); + } + } + if(board) { + var ajax = new XMLHttpRequest(); + ajax.open("GET", self.prefix + "livechan/api/pph?newsgroup="+board); + ajax.onreadystatechange = function () { + if (ajax.readyState == 4 && ajax.status == 200 ) { + var data = JSON.parse(ajax.responseText); + if (data && data.pph !== undefined) { + self.pph = data.pph; + } + } + } + ajax.send(); + } +} + +/* @brief update the user counter for number of users online + */ +Chat.prototype.updateUserStats = function(count) { + var elem = this.chatElems.navbar.status; + elem.textContent = "Online: "+count + " PPH: "+ this.pph; +} + + +/* @brief Scrolls the chat to the bottom. + */ +Chat.prototype.scroll = function() { + this.chatElems.output.scrollTop = this.chatElems.output.scrollHeight; +} + +/** @brief roll over old posts, remove them from ui */ +Chat.prototype.rollover = function() { + /* + var self = this; + var chatSize = self.options.scrollback || 50; + var e = self.chatElems.output; + while ( e.childNodes.length > chatSize ) { + e.childNodes[0].remove(); + } +*/ +} + +/* @brief Inserts the chat into the DOM, overwriting if need be. + * + * @TODO: Actually scan and insert appropriately for varying numbers. + * + * @param outputElem The dom element to insert the chat into. + * @param chat The dom element to be inserted. + * @param number The number of the chat to keep it in order. + */ +Chat.prototype.insertChat = function(chat, data) { + //var number = data.Count; + //var convo = data.Convo; + //if (!number) { + // this.error("Error: invalid chat number."); + //} + var self = this; + // append to main output + var outputElem = this.chatElems.output; + outputElem.appendChild(chat); + // scroll to end + self.scroll(); + self.rollover(); + // show new thread + if (self.lastOp) { + self.chatElems.convobar.show(self.lastOp); + self.lastOp = null; + } +} + + +/* @brief Generates a chat div. + * + * @param data Data passed in via websocket. + * @return A dom element. + */ +Chat.prototype.generateChat = function(data) { + var self = this; + + var chat = document.createElement('div'); + self.chatElems.convobar.update(data.Parent, data); + var convo = self.chatElems.convobar.holder[data.Parent]; + chat.select = function() { + console.log("selecting..."); + convo.select(); + } + chat.className = 'livechan_chat_output_chat livechan_chat_convo_' + convo.id; + var convoLabel = document.createElement('span'); + convoLabel.className = 'livechan_convo_label'; + convoLabel.appendChild(document.createTextNode(data.PostSubject)); + + var header = document.createElement('div'); + header.className = 'livechan_chat_output_header'; + var name = document.createElement('span'); + name.className = 'livechan_chat_output_name'; + var trip = document.createElement('span'); + trip.className = 'livechan_chat_output_trip'; + var date = document.createElement('span'); + date.className = 'livechan_chat_output_date'; + var count = document.createElement('span'); + count.className = 'livechan_chat_output_count'; + + var body = document.createElement('div'); + body.className = 'livechan_chat_output_body'; + var message = document.createElement('div'); + message.className = 'livechan_chat_output_message'; + + + if (data.PostName) { + name.appendChild(document.createTextNode(data.PostName)); + } else { + name.appendChild(document.createTextNode('Anonymous')); + } + + if (data.Files) { + for (var idx = 0 ; idx < data.Files.length ; idx ++ ) { + var file = data.Files[idx]; + if(!file) continue; + var a = document.createElement('a'); + a.setAttribute('target', '_blank'); + // TODO: make these configurable + var filepath = file.Path; + var thumb_url = self.options.prefix + 'thm/'+filepath + ".jpg"; + var src_url = self.options.prefix + 'img/'+filepath; + + a.setAttribute('href',src_url); + var fl = filepath.toLowerCase(); + var img = document.createElement('img'); + img.setAttribute('src', thumb_url); + img.className = 'livechan_image_thumb'; + a.appendChild(img); + message.appendChild(a); + img.onload = function() { self.scroll(); } + + img.addEventListener('mouseover', function () { + + var e = document.createElement("div"); + e.setAttribute("id", "hover_"+data.ShortHash); + e.setAttribute("class", "hover"); + + if (fl.match(/\.(webm|mp4|mkv)$/)) { + // video + var v = document.createElement("video"); + v.src = src_url; + e.appendChild(v); + } else if (fl.match(/\.(mp3|ogg|oga|flac|opus)$/)) { + // audio + var a = document.createElement("audio"); + a.src = src_url; + e.appendChild(a); + } else if (fl.match(/\.txt$/)) { + // + } else { + // image + var i = document.createElement("img"); + i.src = src_url; + e.appendChild(i); + } + chat.appendChild(e); + }); + img.addEventListener('mouseout', function () { + // unload image + var e = document.getElementById("hover_"+data.ShortHash); + e.parentElement.removeChild(e); + }); + } + } + + /* Note that parse does everything here. If you want to change + * how things are rendered modify messageRules. */ + if (data.PostMessage) { + message.appendChild(parse(data.PostMessage, messageRules)); + } else { + message.appendChild(document.createTextNode('')); + } + + if (data.Posted) { + date.appendChild(document.createTextNode((new Date(data.Posted * 1000)).toLocaleString())); + } + + if (data.Tripcode) { + var et = document.createElement('span'); + et.innerHTML = data.Tripcode; + trip.appendChild(et); + } + + if (data.HashShort) { + var h = data.HashShort; + chat.setAttribute('id', 'livechan_chat_'+h); + count.appendChild(document.createTextNode(h)); + count.addEventListener('click', function() { + self.chatElems.input.message.value += '>>'+h+'\n'; + self.chatElems.input.message.focus(); + chat.select(); + }); + } + + header.appendChild(name); + header.appendChild(trip); + header.appendChild(date); + header.appendChild(convoLabel); + header.appendChild(count); + body.appendChild(message); + + chat.appendChild(header); + chat.appendChild(body); + return chat; +} diff --git a/contrib/js/local_storage.js b/contrib/js/nntpchan/local_storage.js similarity index 100% rename from contrib/js/local_storage.js rename to contrib/js/nntpchan/local_storage.js diff --git a/contrib/js/livechan.js b/contrib/js/nntpchan/old-livechan.js similarity index 100% rename from contrib/js/livechan.js rename to contrib/js/nntpchan/old-livechan.js diff --git a/contrib/js/nntpchan/readme.md b/contrib/js/nntpchan/readme.md new file mode 100644 index 0000000..b08a229 --- /dev/null +++ b/contrib/js/nntpchan/readme.md @@ -0,0 +1 @@ +main nntpchan javascript files diff --git a/contrib/js/reply.js b/contrib/js/nntpchan/reply.js similarity index 99% rename from contrib/js/reply.js rename to contrib/js/nntpchan/reply.js index 1044a73..9223ba0 100644 --- a/contrib/js/reply.js +++ b/contrib/js/nntpchan/reply.js @@ -352,6 +352,8 @@ function inject_hover_for_element(elem) { } function init(prefix) { + // because no one cares about this feature :| + return; // inject posthover ... inject_hover_for_element(document); if ( /\.html$/.test(document.location.pathname) && ! (/ukko/.test(document.location.pathname)) ) { diff --git a/contrib/js/theme.js b/contrib/js/nntpchan/theme.js similarity index 100% rename from contrib/js/theme.js rename to contrib/js/nntpchan/theme.js diff --git a/contrib/js/cuckoo_miner.js b/contrib/js/nntpchan/unused/cuckoo_miner.js similarity index 99% rename from contrib/js/cuckoo_miner.js rename to contrib/js/nntpchan/unused/cuckoo_miner.js index c0c28a8..aad3a7b 100644 --- a/contrib/js/cuckoo_miner.js +++ b/contrib/js/nntpchan/unused/cuckoo_miner.js @@ -2,6 +2,7 @@ var easiness = 55.0; var miner_threads = 4; var randoffs = 64; +/* onready(function(){ document.getElementById("start_miner").onclick = function() { var btn = document.getElementById("start_miner"); @@ -61,3 +62,5 @@ onready(function(){ function miner_cb(s) { document.getElementById("miner_result").value = s; } +*/ + diff --git a/contrib/js/nntpchan/unused/readme.md b/contrib/js/nntpchan/unused/readme.md new file mode 100644 index 0000000..2a6e625 --- /dev/null +++ b/contrib/js/nntpchan/unused/readme.md @@ -0,0 +1,2 @@ +this directory holds unused javascript files for nntpchan +don't delete files move them here diff --git a/contrib/js/readme.md b/contrib/js/readme.md new file mode 100644 index 0000000..406e4ab --- /dev/null +++ b/contrib/js/readme.md @@ -0,0 +1,2 @@ +javascript files for nntpchan + diff --git a/contrib/static/banner_0.jpg b/contrib/static/banner_0.jpg index 4777c63..e15534a 100644 Binary files a/contrib/static/banner_0.jpg and b/contrib/static/banner_0.jpg differ diff --git a/contrib/static/banner_1.jpg b/contrib/static/banner_1.jpg index 97a7fcf..a41ced4 100644 Binary files a/contrib/static/banner_1.jpg and b/contrib/static/banner_1.jpg differ diff --git a/contrib/static/banner_2.jpg b/contrib/static/banner_2.jpg index 0c23537..123ca21 100644 Binary files a/contrib/static/banner_2.jpg and b/contrib/static/banner_2.jpg differ diff --git a/contrib/static/banner_3.jpg b/contrib/static/banner_3.jpg new file mode 100644 index 0000000..de02f93 Binary files /dev/null and b/contrib/static/banner_3.jpg differ diff --git a/contrib/static/banner_4.jpg b/contrib/static/banner_4.jpg new file mode 100644 index 0000000..20dea5a Binary files /dev/null and b/contrib/static/banner_4.jpg differ diff --git a/contrib/static/bloodgod.css b/contrib/static/bloodgod.css index 81a0019..e8a1cd2 100644 --- a/contrib/static/bloodgod.css +++ b/contrib/static/bloodgod.css @@ -2,15 +2,14 @@ bloodgod theme css override */ -/** - bloodgod theme css override -*/ - body { color: #666; background: #111; } +.post_body > pre { + color: #666; +} input, textarea, button, input[type="text"], input[type="password"], input[type="checkbox"], input[type="file"], input[type="submit"], @@ -25,7 +24,7 @@ input[type="button"] { color: black; } -#captcha_img, pre { +#captcha_img { background: #D80000; } @@ -110,4 +109,4 @@ table thead th { .origin > img , .not_found > img { -webkit-filter: invert(1); filter: invert(1); -} \ No newline at end of file +} diff --git a/contrib/static/dayman.css b/contrib/static/dayman.css index 96e78b6..cfdcf51 100644 --- a/contrib/static/dayman.css +++ b/contrib/static/dayman.css @@ -16,6 +16,23 @@ body { background: #FBFFC9; } -.navbar, table, thead, th, table, pre { +#postform_container { + background-color: rgba(0,0,0,0); +} + +.post:target { + background-color: #ffda9b; + box-shadow: 0px 0px 5px 1px; +} + +img#nntpchan_banner { + box-shadow: 0px 0px 10px 0px #FFECBE; +} + +#postform_inner { + box-shadow: 0px 1px 5px 1px; +} + +.navbar, table, thead, th, table, pre, .op { background: #FFECBE; } diff --git a/contrib/static/livechan.css b/contrib/static/livechan.css new file mode 100644 index 0000000..5ea3a5e --- /dev/null +++ b/contrib/static/livechan.css @@ -0,0 +1,249 @@ +input { + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + border-radius: 0px; +} + +.livechan_captcha_input { + color: black; +} + +textarea, select { + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + border-radius: 0px; +} + + +.livechan_chat_input { + padding: 0; + margin: 0; + position: fixed; + width: 100%; + bottom: 0; + left: 0; + right: 0; + background: #d6daf0; +} + +.livechan_chat_input_name, .livechan_chat_input_convo { + padding: 0; + padding-left: none; + margin: 0; + width: 80%; + color: black; +} + +.livechan_chat_input_left { + width: 19%; +} + +.livechan_chat_input_message_div { + padding:0; + margin:0; + position: absolute; + width: 70%; + left: 20%; + top: 3px; + bottom: 0; +} + +.livechan_chat_input_message { + padding: 0; + margin: 0; + border:none; + height: 100%; + width: 100%; + resize: none; +} + +.livechan_chat_input_submit { + position: absolute; + width: 8%; + top: 3px; + right: 1px; + bottom: 0; + border: none; + color: black; +} + +.livechan_chat_output { + position: fixed; + top: 20px; + left: 0; + right: 0; + bottom: 60px; + overflow: auto; + width: 89%; + -webkit-overflow-scrolling: touch; +} + +.livechan_chat_output_chat { + max-height: 200px; + overflow: hidden; +} + +.livechan_chat_output_date { + margin: 0 4px; +} + +.livechan_chat_output_count:hover { + cursor: pointer; +} + +.livechan_chat_capcode { + margin: 0 4px; + font-style: italic; + font-weight: lighter; +} + +.livechan_image_thumb { + max-width: 300px; + max-height: 200px; + float: left; + margin: 10px; +} + +.livechan_captcha { + left: 0px; + top: 0px; + bottom: 0px; + right: 0px; + position: fixed; + opacity: 0.9; +} + +.livechan_captcha_inner { + padding: 200px; +} + +.livechan_captcha_image { +} + +.livechan_captcha_input { + float: down; +} + +.livechan_spoiler { + color: black; + background: black; +} + +.livechan_chat_output_chat { + background: #d6daf0; +} + +.livechan_spoiler:hover { + color: white; +} + +.livechan_convo_label { + padding: 5px; +} + +.livechan_convobar_root { + position: fixed; + top: 20px; + right: 0; + width: 10%; +} + +.livechan_convobar_item { + padding: 5px; + margin: 5px; + background: #d6daf0; +} + +.livechan_navbar { + z-index: 3; + position: fixed; + top: 0; + width: 100%; + height: 20px; +} + +.livechan_navbar_mod_indicator_inactive, .livechan_navbar_mod_indicator_active, .livechan_navbar_status, .livechan_navbar_channel_label { + padding-left: 10px; + padding-right: 10px; + background: #d6daf0; +} + + +.hover { + position: relative; + padding: 1px; + left: -1000px + border: 1px dashed black; + visibility: hidden; +} +.hover > img { + position: fixed; + top: 0%; + right: 0; + max-width: 75%; + max-height: 75%; + visibility: visible; +} + +.livechan_captcha, .livechan_convobar_root, #chat { + background: #EEF2FF; +} + +.livechan_chat_output_chat { + font-family: monospace; + margin: 4px; + padding: 4px; +} + +.livechan_chat_output_name { + font-weight: bold; + color: green; +} + +.livechan_chat_output_count:hover { + color: red; +} + +.livechan_greentext { + color: #789922; +} + +.livechan_boldtext { + font-weight: bold; +} + +.livechan_internallink , a { + color: blue; +} + +.livechan_internallink:hover , a { + color: red; + cursor: pointer; +} + +.livechan_chat_selected { + background: blue; +} + + +.livechan_navbar_mod_indicator_active { + background: #4a4ad4; + color: #34d434; +} + +.livechan_navbar_mod_indicator_admin { + background: #4a4ad4; + color: red; +} + +.livechan_navbar_mod_indicator_inactive { + color: #aaaaaa; + background: #EEF2FF; +} + + +.livechan_redtext { + color: #af0a0f; + font-weight: bold; +} + diff --git a/contrib/static/site.css b/contrib/static/site.css index a073499..fd9faab 100644 --- a/contrib/static/site.css +++ b/contrib/static/site.css @@ -89,17 +89,22 @@ textarea { max-height: 2.4em; } +.post_body > pre { + font-size: 10pt; + font-weight: unset; +} + pre { white-space: pre-wrap; align: center; - font-size: 13pt; - background: #98E; + font-size: 12pt; color: black; display: inline-block; overflow-wrap: break-word; word-wrap: break-word; font-weight: bold; - padding: 20px 20px 20px 20px; + + font-family: sans; margin-left: auto; margin-right: auto; @@ -318,26 +323,46 @@ input, textarea { .frontend { margin-top: 0px; display: inline-block; + margin-right: 5px; +} + +.op , .reply { + float: left; + clear: both; } .op { margin-top: 5px; margin-bottom: 1px; - float: left; - clear: both; } .post:target { background-color: #A99AFF; } +.postreply { + float: right; +} + .post { - max-width: 75%; - margin-bottom: 5px; + background-color: #D6DAF0; + + border: 1px solid #B7C5D9; + border-left: none; + border-top: none; + display: table; + padding: 2px; + margin: 2px; + + display: inline-block; + float: left; + clear: both; + min-width: 500px; } .postheader { + width: 100%; padding-top: 3px; padding-right: 5px } @@ -435,10 +460,6 @@ input, textarea { padding-bottom: 0px; } -.post { - display: inline-block; -} - .pagelist { display: inline-block; overflow: hidden; @@ -545,8 +566,20 @@ textarea#reply-text { } .thread { - padding-left: 10%; + padding-left: 10px; padding-top: 10px; padding-bottom: 10px; width: 80%; -} \ No newline at end of file +} + +.hider { + float: right; + +} +@keyframes rotate { + 0% { transform:rotate(0deg); } + 25% { transform:rotate(-1deg); } + 50% { transform:rotate(0deg); } + 75% { transform:rotate(1deg); } + +} diff --git a/contrib/static/tomorrow.css b/contrib/static/tomorrow.css index 0b6cd42..a5b346f 100644 --- a/contrib/static/tomorrow.css +++ b/contrib/static/tomorrow.css @@ -9,6 +9,12 @@ body padding-right: 4px; } +.post_body > pre { + color: #DADADA; + text-align: left; +} + + main, aside, section @@ -336,6 +342,12 @@ textarea margin-bottom: 1px; float: left; clear: both; + background-color: #0f0f0f; + border-color: #242424; +} + +pre { + background-color: #0f0f0f; } .post @@ -621,4 +633,17 @@ hr { border: 1px solid #535353; height: 1px -} \ No newline at end of file +} + +img#nntpchan_banner { + box-shadow: 0px 0px 10px 0px; +} + +#postform_container { + background-color: rgba(0, 0, 0, 0); +} + +.post:target { + background-color: #2c2d3e; + box-shadow: 0px 0px 10px 2px; +} diff --git a/contrib/templates/default/frontpage.mustache b/contrib/templates/default/frontpage.mustache index 34ac293..68e5869 100644 --- a/contrib/templates/default/frontpage.mustache +++ b/contrib/templates/default/frontpage.mustache @@ -16,66 +16,64 @@ {{frontend}} on nntpchan - + {{{navbar}}}
-
-
-

{{frontend}} on nntpchan

-

View the overboard

-

Read the FAQ

-

Join the IRC on rizon or irc2p

-

Lurk on URC

-

Check out the board list

-

Fork on github: frontend and core

-

We've Had {{totalposts}} Posts Since August 01 2015

-
-
-
-
- - - - - + {{# boardgraph}} + + + + + + + {{/ boardgraph}} + +
- {{{postsgraph}}} - - {{! todo: move boardgraph into its own file like postsgraph }} - - - - - - - - - - - {{# boardgraph}} +
+
+
+
+
{{frontend}} on nntpchan
+
View the overboard
+
Join the IRC on rizon or irc2p
+
Check out the board list
+
Fork on github: frontend and core
+
We've Had {{totalposts}} Posts Since August 01 2015
+
+
+
{{#i18n.Translations}}{{board_label}}{{/i18n.Translations}} {{#i18n.Translations}}{{posts_hour}}{{/i18n.Translations}} {{#i18n.Translations}}{{posts_today}}{{/i18n.Translations}} {{#i18n.Translations}}{{total}}{{/i18n.Translations}}
+ + + + - - -
+ {{{postsgraph}}} + + {{! todo: move boardgraph into its own file like postsgraph }} + + - - - - + + + + - {{/ boardgraph}} - -
- {{Board}} - - {{Hour}} - - {{Day}} - - {{All}} - {{#i18n.Translations}}{{board_label}}{{/i18n.Translations}} {{#i18n.Translations}}{{posts_hour}}{{/i18n.Translations}} {{#i18n.Translations}}{{posts_today}}{{/i18n.Translations}} {{#i18n.Translations}}{{total}}{{/i18n.Translations}}
-
- {{{overview}}} + +
+ {{Board}} + + {{Hour}} + + {{Day}} + + {{All}} +
+ + + + + {{{overview}}} +
-

- - + +
diff --git a/contrib/templates/default/live.mustache b/contrib/templates/default/live.mustache index 01a97e7..a5dd838 100644 --- a/contrib/templates/default/live.mustache +++ b/contrib/templates/default/live.mustache @@ -8,34 +8,25 @@ {{#i18n.Translations}}{{overboard_title}}{{/i18n.Translations}} - + + + -
-
-
-
- - {{{navbar}}} - -
-
+ -
-
- - -
diff --git a/contrib/templates/default/post.mustache b/contrib/templates/default/post.mustache index 342a822..b349cdc 100644 --- a/contrib/templates/default/post.mustache +++ b/contrib/templates/default/post.mustache @@ -1,4 +1,23 @@ -
+
{{#post.IsI2P}} @@ -10,17 +29,28 @@ {{#post.IsClearnet}} {{/post.IsClearnet}} - - {{post.Subject}} - {{post.Name}} - - No.{{post.ShortHash}} - [{{#i18n.Translations}}{{reply_label}}{{/i18n.Translations}}] - {{{post.Pubkey}}} + + +
+ Subject: {{post.Subject}} +
+
+ Name: {{post.Name}}{{{post.Pubkey}}} +
+
+ MessageID: {{post.MessageID}} +
+
+ Date: +
+
{{#post.Attachments}} -
+
{{#i18n.Translations}}{{download_prompt}}{{/i18n.Translations}} {{Filename}} @@ -30,6 +60,7 @@ {{/post.Attachments}}
- {{{post.RenderBody}}} -
+
{{{post.RenderBody}}}
+
+
diff --git a/contrib/templates/default/postform.mustache b/contrib/templates/default/postform.mustache index 36b252f..f135674 100644 --- a/contrib/templates/default/postform.mustache +++ b/contrib/templates/default/postform.mustache @@ -77,14 +77,14 @@ - + diff --git a/contrib/templates/default/ukko.mustache b/contrib/templates/default/ukko.mustache index 30a8be4..3288386 100644 --- a/contrib/templates/default/ukko.mustache +++ b/contrib/templates/default/ukko.mustache @@ -23,7 +23,7 @@
{{#i18n.Translations}}{{overboard_title}}{{/i18n.Translations}}
-
livechan
+
livechan
{{#threads}}
diff --git a/doc/README.md b/doc/README.md index 9ae1c77..66a5425 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,5 +1,6 @@ NNTPChan Documentation ====================== +**WARNING: Caching with redis was deprecated in [commit 96de42](https://github.com/majestrate/srndv2/commit/96de42bf5d689a54d27871c9f8bc4ef3d0cdbefc). Any reference to redis as a cache should be ignored. You should instead use null cache.** Hey, welcome to the documentation. This will help you use and develop with NNTPChan. @@ -9,7 +10,7 @@ Hey, welcome to the documentation. This will help you use and develop with NNTPC 2. [Setting up NNTPChan](setting-up.md) - Configuring the node 3. [Running NNTPChan](running.md) - Running the node for the first time 4. [Managing your NNTPChan node with the CLI](cli.md) - Manage many aspects of your node via the command-line interface -5. [Configuring your news reader for NNTPChan](extras/configure-newsreader.md) - Setup **Mozilla Thunderbird** or **Pan** to send and receive articles from your node. +5. [Configuring your news reader for NNTPChan](extras/configure-newsreader.md) - Setup **Mozilla Thunderbird** or **Pan** to send and receive articles from your NNTPChan node of choice. ##Developer related diff --git a/doc/building.md b/doc/building.md index 961213b..662bf3e 100644 --- a/doc/building.md +++ b/doc/building.md @@ -8,7 +8,7 @@ This document will help you build the NNTPChan software from the source code. NNTPChan can run on the following operating systems: * Linux - * Instructions are available for Debian and Trisquel. + * Instructions are available for Debian and [Trisquel](#trisquel-instructions-wip). * FreeBSD Dependancies: diff --git a/doc/database/postgres/configure-postgres.md b/doc/database/postgres/configure-postgres.md index c870c6a..ebe3675 100644 --- a/doc/database/postgres/configure-postgres.md +++ b/doc/database/postgres/configure-postgres.md @@ -4,23 +4,10 @@ Configuring Postgres database These are instructions for setting up NNTPChan with Postgres as the data-storage system. ##Configuring Postgres +A user with sufficient privileges to run su is required (hint: you can use root). This command switches to the Postgres user, creates a Postgres role called `srnd`, and prompts for a password. For illustrative purposes, we will use `srnd` as the password. -Setting up postgres (as root): - - # become postgres user - su postgres - # spawn postgres admin shell - psql - -You'll get a prompt, enter the following: - - CREATE ROLE srnd WITH LOGIN PASSWORD 'srnd'; - CREATE DATABASE srnd WITH ENCODING 'UTF8' OWNER srnd; - \q - -For demo purposes we'll use these credentials. -These are default values, please change them later. + # su - postgres -c "createuser --pwprompt --createdb --encrypted srnd" ###Important -These credentials assume you are going to run using a user called `srnd`, if your username you plan to run the daemon as is different please change `srnd` to your username. +It's easiest to connect to Postgres using role-based authentication. In this case, our Linux user `srnd` matches up with our Postgres role `srnd`, so role-based authentication can take place. If you're running SRNDv2 as a different user (e.g. `nntpchan`), you will need to create a role that matches that user using the command above. diff --git a/doc/srnd.md b/doc/srnd.md index b394243..7dedf59 100644 --- a/doc/srnd.md +++ b/doc/srnd.md @@ -16,6 +16,7 @@ allow_attachments=1 require_tls=1 anon_nntp=0 feeds=/etc/nntpchan/feeds.d +archive=0 [pprof] enable=0 @@ -82,17 +83,19 @@ This is where you put the address and port that you would like the NNTP server t ####sync_on_start * When this is set to `1` your NNTP server will sync articles with its peers on startup. -* When this is set to `0` then no syncing will take place on statup. +* When this is set to `0` then no syncing will take place on startup. ####allow_anon -* When this is set to `1` bluh. -* When this is set to `0` bluh. +* When this is set to `1`, posts made from anonymizing networks will be synced from peers. +* When this is set to `0`, posts made from anonymizing networks will not be synced from peers. ####allow_anon_attachments -* When this is set to `1` bluh. -* When this is set to `0` bluh. +* When this is set to `1`, attachments posted from anonymizing networks will be syncdd from peers. +* When this is set to `0`, attachments posted from anonymizing networks will not be synced from peers. + +Nodes with `allow_anon_attachments` disabled will not receive threads with images posted from anonymizing networks. Likewise, the thread replies will not sync. In the case where an anonymized user posts an image reply and the node has `allow_anon_attachments` disabled, text posts without attachments replying to the non-synced image post will appear to be "ghosted". ####allow_attachments @@ -112,6 +115,9 @@ This is where you put the address and port that you would like the NNTP server t ####feeds * Feeds configurations can optionally be stored in a directory of your choosing (the default is `feeds.d` in the working directory). Any ini files located in this directory will be loaded. +####archive +* When this is set to `1`, the daemon will never expire posts. +* When this is set to `0`, the daemon will delete old posts. FIXME: under what conditions? ##`[pprof]` @@ -129,8 +135,8 @@ All pprof-related settings. ##`[frontend]` #####minimize_html -* `0`: do not minimize HTML -* `1`: minimize HTML +* `0`: Do not minimize HTML +* `1`: Minimize HTML ##Placing configuration elsewhere