├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── clean_all.sh
├── clean_content.sh
├── clean_logs.sh
├── clean_pids.sh
├── client
├── img
│ ├── attachment.jpg
│ ├── banner.jpg
│ ├── fade-blue.png
│ └── favicon.ico
├── inc
│ ├── controller.js
│ ├── functions.js
│ ├── page.js
│ ├── pagehtml.js
│ ├── post.js
│ └── serverbridge.js
├── index.html
├── smugchan.js
└── styles.css
├── common
├── inputhandler.js
├── package.json
├── payloads.js
├── settings.js
├── sio.js
├── slog.js
├── storage.js
├── storagebrowser.js
├── util.js
└── wrapperfuncs.js
├── install_all.sh
├── kill_all.sh
├── nameserver
├── namemanager.js
└── package.json
├── populate_boards.sh
├── scripts
├── addboardtosite.js
├── addthreadtoboard.js
├── createboard.js
├── createmod.js
├── createserver.js
├── createsite.js
└── createthread.js
├── server
├── package.json
└── server.js
├── setup.sh
└── start_all.sh
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": true
6 | },
7 | "extends": "eslint:recommended",
8 | "parserOptions": {
9 | "sourceType": "module"
10 | },
11 | "rules": {
12 | "indent": [
13 | "error",
14 | 4
15 | ],
16 | "linebreak-style": [
17 | "error",
18 | "unix"
19 | ],
20 | "quotes": [
21 | "error",
22 | "single"
23 | ],
24 | "semi": [
25 | "error",
26 | "always"
27 | ],
28 | "eqeqeq": [
29 | "warn",
30 | "smart"
31 | ],
32 | "no-console": [
33 | "off"
34 | ],
35 | "brace-style": [
36 | "error",
37 | "1tbs"
38 | ],
39 | "curly": [
40 | "error",
41 | "all"
42 | ]
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # ipfs repo
61 | .ipfs/
62 |
63 | # logs
64 | nameserver/log.txt
65 | server/log.txt
66 |
67 | # locks
68 | common/package-lock.json
69 | nameserver/package-lock.json
70 | server/package-lock.json
71 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 smugdev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Smugboard
2 | An IPFS-based imageboard.
3 |
4 | ## Prerequisites
5 | * Node
6 |
7 | * Browserify
8 |
9 | * go-ipfs
10 |
11 | * eslint (if you want to contribute)
12 |
13 | ## Setup
14 | ```
15 | ./install_all.sh
16 | ./start_all.sh
17 | ./setup.sh
18 | ```
19 |
20 | ## Usage
21 |
22 | The site can be accessed from any local go-ipfs daemon, provided that it is configured with
23 | ```
24 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin "[\"*\"]"
25 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials "[\"true\"]"
26 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods "[\"PUT\", \"POST\", \"GET\"]"
27 | ```
28 | beforehand. Then, simply visit the link as output by setup.sh. Make sure to restart the daemon after updating the configuration.
29 |
--------------------------------------------------------------------------------
/clean_all.sh:
--------------------------------------------------------------------------------
1 | rm -rf .ipfs
2 | rm -rf common/node_modules
3 | rm -rf nameserver/node_modules
4 | rm -rf server/node_modules
5 |
6 | ./clean_content.sh
7 | ./clean_logs.sh
8 | ./clean_pids.sh
9 |
--------------------------------------------------------------------------------
/clean_content.sh:
--------------------------------------------------------------------------------
1 | rm -f nameserver/bindings.json
2 | rm -f client/bundle.js
3 | rm -f server/wrappers.json
4 |
--------------------------------------------------------------------------------
/clean_logs.sh:
--------------------------------------------------------------------------------
1 | rm -f server/log.txt
2 | rm -f nameserver/log.txt
3 |
4 |
--------------------------------------------------------------------------------
/clean_pids.sh:
--------------------------------------------------------------------------------
1 | rm -f .ipfs/ipfs.pid
2 | rm -f server/server.pid
3 | rm -f nameserver/server.pid
4 |
--------------------------------------------------------------------------------
/client/img/attachment.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smugdev/smugboard/16fb615c0c4aee435dfd2d0c8df77e740501a26d/client/img/attachment.jpg
--------------------------------------------------------------------------------
/client/img/banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smugdev/smugboard/16fb615c0c4aee435dfd2d0c8df77e740501a26d/client/img/banner.jpg
--------------------------------------------------------------------------------
/client/img/fade-blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smugdev/smugboard/16fb615c0c4aee435dfd2d0c8df77e740501a26d/client/img/fade-blue.png
--------------------------------------------------------------------------------
/client/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smugdev/smugboard/16fb615c0c4aee435dfd2d0c8df77e740501a26d/client/img/favicon.ico
--------------------------------------------------------------------------------
/client/inc/controller.js:
--------------------------------------------------------------------------------
1 | var slog = require('../../common/slog.js');
2 | var page = require('./page.js');
3 | var functions = require('./functions.js');
4 | var wrapperFuncs = require('../../common/wrapperfuncs.js');
5 | //var handler = require('../../common/inputhandler.js');
6 |
7 | var secondsLeft = 30;
8 | var pendingRefresh = false;
9 | var refreshTimeout = null;
10 |
11 | //after .last load
12 | function mergeToClientInitial(wrapper){
13 | if (wrapper.last.data.info.mode === 'site'){
14 | //handle title bar
15 | //TODO
16 | } else if (wrapper.last.data.info.mode === 'board'){
17 | //handle the title bar and stuff, handle banners
18 | } else if (wrapper.last.data.info.mode === 'thread'){
19 | //do nothing(?)
20 | }
21 | }
22 |
23 | //after sLog load
24 | function mergeToClientMidpoint(wrapper, addressObj){
25 | if (wrapper.last.data.info.mode === 'site'){
26 | //handle board list
27 | let sitePublicAddress;
28 | for (let address of addressObj) {
29 | if (address.mode === 'site') {
30 | sitePublicAddress = address.publicAddress;
31 | }
32 | }
33 | page.fillBoardlist(sitePublicAddress, wrapper.sLog);
34 | } else if (wrapper.last.data.info.mode === 'board'){
35 | //do nothing(?)
36 | } else if (wrapper.last.data.info.mode === 'thread'){
37 | //do nothing(?)
38 | }
39 | }
40 |
41 | //everything is loaded, and this is the endpoint
42 | function mergeToClientEndpoint(id, wrappers, knownMods, addressObj, skipMerges){
43 | if (!skipMerges){
44 | if (wrappers[id].last.data.info.mode === 'site'){
45 | //display site
46 | page.setSiteHTML(addressObj);
47 | //page.setNavLinks(false, addressObj);
48 | } else if (wrappers[id].last.data.info.mode === 'board'){
49 | //display board, or catalog if addressParsed[addressParsed.length - 1].mode === 'catalog'
50 | if (addressObj[addressObj.length - 1].mode === 'catalog'){
51 | page.setCatalogHTML(addressObj);
52 | } else {
53 | page.setBoardHTML(addressObj);
54 | }
55 | page.setPostFormData(id);
56 | page.setNavLinks(false, addressObj);
57 | page.setHeader(wrappers, addressObj);
58 | } else if (wrappers[id].last.data.info.mode === 'thread'){
59 | //display thread
60 | page.setThreadHTML();
61 | page.setNavLinks(true, addressObj);
62 | page.setHeader(wrappers, addressObj);
63 | page.setPostFormData(id);
64 | }
65 | }
66 |
67 | if (wrappers[id].last.data.info.mode === 'site'){
68 | //display site
69 | let sitePublicAddress;
70 | for (let address of addressObj) {
71 | if (address.mode === 'site') {
72 | sitePublicAddress = address.publicAddress;
73 | }
74 | }
75 | return page.fillBoardlistTable(sitePublicAddress, id, wrappers);
76 | } else if (wrappers[id].last.data.info.mode === 'board'){
77 | //display board, or catalog if addressParsed[addressParsed.length - 1].mode === 'catalog'
78 | if (addressObj[addressObj.length - 1].mode === 'catalog'){
79 | return page.loadCatalog(id, wrappers, knownMods);
80 | } else {
81 | return page.loadBoard(id, wrappers, addressObj, knownMods);
82 | }
83 | } else if (wrappers[id].last.data.info.mode === 'thread'){
84 | //display thread
85 | return page.loadThread(null, id, wrappers, addressObj, knownMods, false, false).then(() => {
86 | refreshThread(id)();
87 | document.getElementById('update_thread').setAttribute('onclick', 'window.smug.forceRefresh("' + id + '")');
88 | if (addressObj[addressObj.length - 1].mode === 'post'){
89 | //TODO scroll to post
90 | }
91 | return;
92 | });
93 | }
94 | }
95 |
96 | function handleWrapperRecursive(addressObj, wrappers, index, skipMerges, knownMods, modsPending){
97 | if (modsPending == null) {
98 | modsPending = [];
99 | }
100 | if (knownMods == null) {
101 | knownMods = [];
102 | }
103 | let currentId = addressObj[index].actualAddress;
104 | let endpoint = false;
105 |
106 | return wrapperFuncs.pullWrapperStub(currentId, wrappers).then(() => {
107 | modsPending = modsPending.concat(wrapperFuncs.collectMods(wrappers[currentId].last.data.info.mods, wrappers));
108 | if (wrappers[currentId].last.data.info.mods != null) {
109 | knownMods = knownMods.concat(wrappers[currentId].last.data.info.mods);
110 | }
111 | addressObj[index].mode = wrappers[currentId].last.data.info.mode;
112 | if (!skipMerges) {
113 | mergeToClientInitial(wrappers[currentId]);
114 | }
115 |
116 | if (index === addressObj.length - 1){
117 | //last item
118 | endpoint = true;
119 | } else if (wrappers[currentId].last.data.info.mode === 'board' && addressObj.length - 1 === index + 1 && addressObj[index + 1].publicAddress === 'catalog'){
120 | //catalog designator
121 | addressObj[index + 1].mode = 'catalog';
122 | endpoint = true;
123 | } else if (wrappers[currentId].last.data.info.mode === 'thread' && addressObj.length - 1 === index + 1){
124 | //next item is just the post #
125 | addressObj[index + 1].mode = 'post';
126 | endpoint = true;
127 | }
128 |
129 | let promises = [];
130 | promises.push(
131 | wrapperFuncs.loadWrapper(wrappers[currentId]).then(() => {
132 | return slog.calculateSLogPayloads(wrappers[currentId]);
133 | })
134 | );
135 | if (endpoint){
136 | promises = promises.concat(modsPending);
137 | }
138 | return Promise.all(promises);
139 |
140 | }).then(() => {
141 | if (!skipMerges) {
142 | mergeToClientMidpoint(wrappers[currentId], addressObj);
143 | }
144 |
145 | if (endpoint){
146 | return mergeToClientEndpoint(currentId, wrappers, knownMods, addressObj, skipMerges);
147 | } else {
148 | //something else left, continue recursing
149 | addressObj[index + 1].actualAddress = functions.findAssociation(addressObj[index + 1].publicAddress, wrappers[currentId].sLog);
150 | if (addressObj[index + 1].actualAddress == null) {
151 | return Promise.reject('Item not found: ' + addressObj[index + 1].publicAddress);
152 | } else {
153 | return handleWrapperRecursive(addressObj, wrappers, index + 1, skipMerges, knownMods, modsPending);
154 | }
155 | }
156 | });
157 | //.catch(console.error);
158 | }
159 |
160 |
161 | /*function refresh(id, knownMods, wrappers, addressObj){
162 | //Promise.all for wrappers[id] (use wrapperFuncs.pullWrapperStub -> loadWrapper) and for each mod of knownMods (use wrapperFuncs.collectMods)
163 |
164 |
165 | let modsPending = wrapperFuncs.collectMods(knownMods, wrappers);
166 | let threadId = null;
167 | for (let item of addressObj){
168 | if (item.mode === 'thread'){
169 | threadId = item.actualAddress;
170 | }
171 | }
172 |
173 | let promises = modsPending.concat();
174 |
175 | Promise.all(modsPending)
176 | //page.loadThread(null, threadId, wrappers, addressObj, knownMods, modMode, fromIndex, indexSeqno)
177 | }*/
178 |
179 | function forceRefresh(id){
180 | pendingRefresh = true;
181 | refreshThread(id)();
182 | }
183 |
184 | function queueRefresh(){
185 | secondsLeft = 5;
186 | }
187 |
188 | //function refreshThread(id, wrappers, knownMods, addressObj){
189 | function refreshThread(id){
190 | /*let refreshData = {};
191 | refreshData.id = id;
192 | refreshData.wrappers = wrappers;//use global?
193 | refreshData.knownMods = knownMods;
194 | refreshData.addressObj = addressObj;
195 |
196 | var smug = window.smug || {};
197 | smug.refreshData = refreshData;*/
198 |
199 |
200 | haltRefresh();
201 | let index = 0;
202 | for (let i = 0; i < window.smug.addressObj.length; i++){
203 | if (window.smug.addressObj[i].mode === 'thread'){
204 | index = i;
205 | }
206 | }
207 |
208 | return () => {
209 | if (secondsLeft === 0 || pendingRefresh){
210 | handleWrapperRecursive(window.smug.addressObj, window.smug.wrappers, index, true, window.smug.knownMods);
211 | //handleWrapperRecursive(addressObj, wrappers, index, true, knownMods);
212 | //refresh(id, knownMods, wrappers, addressObj);
213 | document.getElementById('update_secs').innerHTML = 'Updating...';
214 | secondsLeft = 30;
215 | pendingRefresh = false;
216 | } else {
217 | secondsLeft--;
218 | document.getElementById('update_secs').innerHTML = secondsLeft;
219 | refreshTimeout = window.setTimeout(refreshThread(id), 1000);
220 | }
221 | };
222 | }
223 |
224 | function haltRefresh(){
225 | clearTimeout(refreshTimeout);
226 | }
227 |
228 | module.exports.forceRefresh = forceRefresh;
229 | module.exports.queueRefresh = queueRefresh;
230 | //module.exports.refreshThread = refreshThread;
231 | module.exports.handleWrapperRecursive = handleWrapperRecursive;
232 | module.exports.haltRefresh = haltRefresh;
233 |
--------------------------------------------------------------------------------
/client/inc/functions.js:
--------------------------------------------------------------------------------
1 | var util = require('../../common/util.js');
2 |
3 | function parseLocationAddress(windowAddress){
4 | windowAddress = windowAddress.replace(/^#/, '');
5 | var addressInternals = [];
6 |
7 | var addressArr = windowAddress.split('/');
8 |
9 | for (let i = 0; i < addressArr.length; i++){
10 | if (addressArr[i] === ''){
11 | //do nothing
12 | } else if (addressArr[i] === 'ipns' || addressArr[i] === 'ipfs'){
13 | addressInternals.push({
14 | actualAddress: '/' + addressArr[i] + '/' + addressArr[i+1],
15 | publicAddress: '/' + addressArr[i] + '/' + addressArr[i+1]
16 | });
17 | i++;
18 | } else if (addressInternals.length === 0) {
19 | //assume IPNS
20 | addressInternals.push({
21 | actualAddress: '/ipns/' + addressArr[i],
22 | publicAddress: addressArr[i]
23 | });
24 | } else {
25 | addressInternals.push({
26 | publicAddress: addressArr[i]
27 | });
28 | }
29 | }
30 |
31 | return addressInternals;
32 | }
33 |
34 | function getNewHash(addressParsed, unwantedModes, extras){
35 | if (unwantedModes == null) {
36 | unwantedModes = [];
37 | }
38 |
39 | var newHash = '#';
40 | for (let item of addressParsed){
41 | if (item.mode != null && unwantedModes.indexOf(item.mode) === -1){
42 | if (newHash === '#'){
43 | newHash += item.publicAddress;
44 | } else {
45 | newHash += '/' + item.publicAddress;
46 | }
47 | }
48 | }
49 |
50 | if (extras != null) {
51 | for (let item of extras) {
52 | newHash += '/' + item;
53 | }
54 | }
55 |
56 | return newHash;
57 | }
58 |
59 | function scrollToCurrentPost(){
60 | let targetID = location.hash.replace(/^#/, '');
61 | let target = document.getElementById(targetID);
62 | if (target != null) {
63 | document.body.scrollTop = target.offsetTop;
64 | }
65 | }
66 |
67 | function findAssociation(publicAddress, sLog){
68 | for (let entry of sLog){
69 | if (!entry.removed && util.propertyExists(entry, 'data.payload.data.thread.address') && entry.data.seqno){
70 | //if (!entry.removed && entry.data && entry.data.payload && entry.data.seqno && entry.data.payload.data && entry.data.payload.data.thread && entry.data.payload.data.thread.address){
71 | if (publicAddress === entry.data.seqno + ''){
72 | return entry.data.payload.data.thread.address;
73 | }
74 | }
75 | if (!entry.removed && util.propertyExists(entry, 'data.payload.data.uri')){
76 | //if (!entry.removed && entry.data && entry.data.payload && entry.data.payload.data && entry.data.payload.data.uri){
77 | if (publicAddress === entry.data.payload.data.uri){
78 | return entry.data.payload.data.address;
79 | }
80 | }
81 | }
82 | return null;
83 | }
84 |
85 | module.exports.parseLocationAddress = parseLocationAddress;
86 | module.exports.getNewHash = getNewHash;
87 | module.exports.scrollToCurrentPost = scrollToCurrentPost;
88 | module.exports.findAssociation = findAssociation;
89 |
90 |
--------------------------------------------------------------------------------
/client/inc/page.js:
--------------------------------------------------------------------------------
1 | var pageHtml = require('./pagehtml.js');
2 | var functions = require('./functions.js');
3 | var wrapperFuncs = require('../../common/wrapperfuncs.js');
4 | var slog = require('../../common/slog.js');
5 |
6 | var browserDisplayableFormats = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico'];
7 |
8 | function decoratePostBody(bodyRaw, indexSeqno, addressObj){
9 | let bodyPretty = bodyRaw + '';
10 |
11 | bodyPretty = bodyPretty.replace(/&/g, '&');
12 | bodyPretty = bodyPretty.replace(//g, '>');
14 | bodyPretty = bodyPretty.replace(/(>.*)(\r|\n|$)/g, '$1$2');
15 |
16 | //TODO strikethrough
17 | bodyPretty = bodyPretty.replace(/\*\*(.*)\*\*/g, '$1');
18 | bodyPretty = bodyPretty.replace(/==(.*)==/g, '$1');
19 | bodyPretty = bodyPretty.replace(/'''(.*)'''/g, '$1');
20 | bodyPretty = bodyPretty.replace(/''(.*)''/g, '$1');
21 |
22 | let modes = ['site', 'board', 'thread', 'post'];
23 | bodyPretty = bodyPretty.replace(/>>>>>\/([a-z0-9][a-z0-9]*)\/([a-z0-9][a-z0-9]*)\/(\d+)\/(\d+)/g, '>>>>>/$1/$2/$3/$4');
24 | modes = ['board', 'thread', 'post'];
25 | bodyPretty = bodyPretty.replace(/>>>>\/([a-z0-9][a-z0-9]*)\/(\d+)\/(\d+)/g, '>>>>/$1/$2/$3');
26 | modes = ['thread', 'post'];
27 | bodyPretty = bodyPretty.replace(/>>>\/(\d+)\/(\d+)/g, '>>>/$1/$2');//TODO i think you could break this by putting a '$' in the url hash
28 | modes = ['post'];
29 | bodyPretty = bodyPretty.replace(/>>(\d+)/g, '>>$1');//TODO remove class
30 |
31 | return bodyPretty;
32 | }
33 |
34 | function pad(n) {
35 | return (n < 10) ? ('0' + n) : n;
36 | }
37 |
38 | function fillPost(postDiv, post, seqno, timestamp, addressObj, viewingFromIndex, indexSeqno, mod, flagged){
39 | //var post = JSON.parse(postJson);
40 |
41 | if (seqno === 1){
42 | postDiv.setAttribute('class', 'post op');
43 | } else {
44 | postDiv.setAttribute('class', 'post reply');
45 | }
46 |
47 | if (flagged) {
48 | postDiv.setAttribute('class', postDiv.getAttribute('class') + ' flagged');
49 | }
50 |
51 | let extras = [];
52 | if (indexSeqno) {
53 | extras.push(indexSeqno);
54 | }
55 | extras.push(seqno);
56 | let postFullLink = document.createElement('div');
57 | postFullLink.setAttribute('id', functions.getNewHash(addressObj, ['post'], extras));
58 |
59 | let postIntro = document.createElement('p');
60 | postIntro.setAttribute('class', 'intro');
61 |
62 | if (post.subject != null && post.subject !== ''){
63 | let subjectSpan = document.createElement('span');
64 | subjectSpan.setAttribute('class', 'subject');
65 | subjectSpan.appendChild(document.createTextNode(post.subject));
66 | postIntro.appendChild(subjectSpan);
67 | } else {
68 | let subjectSpan = document.createElement('span');
69 | subjectSpan.setAttribute('class', 'blanksubject');
70 | postIntro.appendChild(subjectSpan);
71 | }
72 |
73 | if (post.author != null){
74 | let nameSpan;
75 | if (post.email != null && post.email !== ''){
76 | nameSpan = document.createElement('a');
77 | nameSpan.setAttribute('class', 'email');
78 | nameSpan.setAttribute('href', 'mailto:' + post.email);
79 | } else {
80 | nameSpan = document.createElement('span');
81 | nameSpan.setAttribute('class', 'name');
82 | }
83 | nameSpan.appendChild(document.createTextNode(post.author));
84 | postIntro.appendChild(nameSpan);
85 | }
86 |
87 | if (timestamp != null){
88 | let date = new Date(timestamp);
89 | let timeString = date.getFullYear() + '-' + pad(date.getMonth()+1) + '-' + pad(date.getDate()) + ' ' + pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds());
90 | let dateSpan = document.createElement('span');
91 | dateSpan.setAttribute('class', 'time');
92 | dateSpan.appendChild(document.createTextNode(timeString));
93 | postIntro.appendChild(dateSpan);
94 | }
95 |
96 | if (seqno != null){
97 | let postNum = document.createElement('a');
98 | postNum.setAttribute('id', '' + seqno);
99 | postNum.setAttribute('class', 'post_no');
100 | postNum.setAttribute('href', functions.getNewHash(addressObj, ['post'], extras));
101 | postNum.appendChild(document.createTextNode('No. '));
102 | postIntro.appendChild(postNum);
103 | postIntro.appendChild(document.createTextNode(seqno));
104 | }
105 |
106 | if (viewingFromIndex && seqno === 1 && indexSeqno != null){
107 | let replyLink = document.createElement('a');
108 | replyLink.setAttribute('class', 'reply_link');
109 | replyLink.setAttribute('href', functions.getNewHash(addressObj, ['post', 'thread'], [indexSeqno]));//location.hash + '/' + indexSeqno);//postDiv.getAttribute('data-thread'));//TODO this should be able to handle detached threads
110 | replyLink.appendChild(document.createTextNode('[Reply]'));
111 | postIntro.appendChild(replyLink);
112 | }
113 |
114 | let postFiles = document.createElement('div');
115 | if (post.files != null && post.files.length > 0){
116 | postFiles.setAttribute('class', 'files');
117 | for (let file of post.files){
118 | if (file.address != null && file.size != null){
119 | let fileDiv = document.createElement('div');
120 | fileDiv.setAttribute('class', 'file');
121 | let fileInfo = document.createElement('p');
122 | fileInfo.setAttribute('class', 'fileinfo');
123 | let fileSpan = document.createElement('span');
124 | fileSpan.appendChild(document.createTextNode('File: '));
125 | let fileLink = document.createElement('a');
126 | fileLink.setAttribute('href', file.address);
127 | fileLink.appendChild(document.createTextNode(file.filename));
128 |
129 | let fileDetails = document.createElement('span');
130 | fileDetails.setAttribute('class', 'unimportant');
131 | let fileDims = '';
132 | if (file.width != null && file.height != null){
133 | fileDims = ', ' + file.width + 'x' + file.height;
134 | }
135 | fileDetails.appendChild(document.createTextNode(' (' + (Math.round((parseFloat(file.size) / 1024.0) * 100)/100.0) + 'KiB' + fileDims + ')'));
136 |
137 | let fileImgLink = document.createElement('a');
138 | fileImgLink.setAttribute('href', file.address);
139 | let fileImg = document.createElement('img');
140 | fileImg.setAttribute('class', 'post-image');
141 |
142 | if (file.extension != null && browserDisplayableFormats.indexOf(file.extension) !== -1 && file.height != null && file.width != null){
143 | fileImg.setAttribute('src', file.address);
144 |
145 | let maxDim = 255;
146 | let imgWidth = parseInt(file.width);
147 | let imgHeight = parseInt(file.height);
148 | let longSide = imgHeight > imgWidth ? imgHeight : imgWidth;
149 |
150 | if (longSide > maxDim){
151 | let factor = longSide / maxDim;
152 | imgHeight /= factor;
153 | imgWidth /= factor;
154 | }
155 |
156 | fileImg.setAttribute('style', 'width: ' + imgWidth + 'px; height: ' + imgHeight + 'px;');
157 | } else {
158 | fileImg.setAttribute('src', '/ipfs/QmSmVimunQTsmau3SH9ohESmzRNLCdXGjSHYpxvp9SeZp2');//TODO shouldn't have direct IPFS hashes in here
159 | }
160 |
161 |
162 | fileSpan.appendChild(fileLink);
163 | fileSpan.appendChild(fileDetails);
164 | fileInfo.appendChild(fileSpan);
165 | fileImgLink.appendChild(fileImg);
166 | fileDiv.appendChild(fileInfo);
167 | fileDiv.appendChild(fileImgLink);
168 |
169 | postFiles.appendChild(fileDiv);
170 | }
171 | }
172 | }
173 |
174 | let modControls = document.createElement('span');
175 | if (mod){
176 | modControls.setAttribute('class', 'controls');
177 | let modDelete = document.createElement('a');
178 | modDelete.setAttribute('href', '#');
179 | if (flagged){
180 | modDelete.setAttribute('onclick', 'return smug.modOperation("undelete", "' + mod + '", "' + postDiv.getAttribute('data-thread') + '", "' + seqno + '", smug.refreshPage)');
181 | modDelete.innerHTML = '[-D]';
182 | } else {
183 | modDelete.setAttribute('onclick', 'return smug.modOperation("delete", "' + mod + '", "' + postDiv.getAttribute('data-thread') + '", "' + seqno + '", smug.refreshPage)');
184 | modDelete.innerHTML = '[D]';
185 | }
186 | let modDeleteAll = document.createElement('a');
187 | modDeleteAll.setAttribute('href', '#');
188 | modDeleteAll.setAttribute('onclick', 'return smug.modOperation("deleteall", "' + mod + '", "' + postDiv.getAttribute('data-thread') + '", "' + seqno + '", smug.refreshPage)');
189 | modDeleteAll.innerHTML = '[D+]';
190 |
191 | let modBan = document.createElement('a');
192 | modBan.setAttribute('href', '#');
193 | modBan.setAttribute('onclick', 'return smug.modOperation("ban", "' + mod + '", "' + postDiv.getAttribute('data-thread') + '", "' + seqno + '", smug.refreshPage)');
194 | modBan.innerHTML = '[B]';
195 |
196 | let modBanDelete = document.createElement('a');
197 | modBanDelete.setAttribute('href', '#');
198 | modBanDelete.setAttribute('onclick', 'return smug.modOperation("bandelete", "' + mod + '", "' + postDiv.getAttribute('data-thread') + '", "' + seqno + '", smug.refreshPage)');
199 | modBanDelete.innerHTML = '[B&D]';
200 |
201 | modControls.appendChild(modDelete);
202 | modControls.appendChild(modDeleteAll);
203 | modControls.appendChild(modBan);
204 | modControls.appendChild(modBanDelete);
205 | }
206 |
207 | let postBody = document.createElement('div');
208 | if (post.body != null){
209 | postBody.setAttribute('class', 'body');
210 | let postLine = document.createElement('p');
211 | postLine.setAttribute('class', 'body-line ltr');
212 | postLine.innerHTML = decoratePostBody(post.body, indexSeqno, addressObj);
213 | postBody.appendChild(postLine);
214 | }
215 |
216 | postDiv.appendChild(postFullLink);
217 | postDiv.appendChild(postIntro);
218 | postDiv.appendChild(postFiles);
219 | postDiv.appendChild(modControls);
220 | postDiv.appendChild(postBody);
221 | }
222 |
223 | function getCatalogEmptyThread(){//TODO replace with line in page_html.js
224 | var outer = document.createElement('div');
225 | outer.setAttribute('class', 'mix');
226 | outer.setAttribute('style', 'display: inline-block;');
227 |
228 | var inner = document.createElement('div');
229 | inner.setAttribute('class', 'thread grid-li grid-size-small');
230 |
231 | outer.appendChild(inner);
232 |
233 | return outer;
234 | }
235 |
236 | function loadCatalog(id, wrappers, addressObj, knownMods){
237 | var threadStub = document.getElementById('Grid');
238 |
239 | if (!wrappers[id].sLog) {
240 | return;
241 | }
242 |
243 | let promises = [];
244 | for (let item of wrappers[id].sLog){
245 | if ((!modMode && item.removed) || !(item.data && item.data.payload && item.data.payload.data && item.data.payload.data.thread)){
246 | continue;
247 | }
248 | let threadDiv = getCatalogEmptyThread();
249 | threadDiv.setAttribute('id', item.address);
250 | threadStub.appendChild(threadDiv);
251 |
252 | let threadId = item.data.payload.data.thread.address;
253 | promises.push(
254 | wrapperFuncs.pullWrapperStub(threadId, wrappers).then(() => {
255 | if (wrappers[threadId].last.data.info.mode !== 'thread'){
256 | return Promise.reject('Item ' + threadId + ' is not a valid thread.');
257 | } else {
258 | return slog.getSLogObj(wrappers[threadId].last.data.info.op);
259 | //let promisesInner = [];
260 | //promisesInner.push(slog.getSLogObj(wrappers[threadId].last.data.info.op));
261 | //promisesInner.push(slog.updateSLog(wrappers[threadId].sLog, wrappers[threadId].last.data.head, 3));
262 | //return Promise.all(promisesInner);
263 | }
264 | }).then(value => {
265 | let sLog = [value];
266 | //if (values[1][0].data.seqno !== 1){
267 | //sLog.push(value);
268 | //}
269 | //sLog = sLog.concat(values[1]);
270 | wrappers[threadId].sLog = sLog;
271 |
272 | return loadCatalogStub(threadDiv, threadId, wrappers, addressObj, knownMods, modMode, true, item.data.seqno);
273 | })
274 | );
275 | }
276 | return Promise.all(promises);
277 | }
278 |
279 | function loadCatalogStub(threadDiv, id, wrappers, addressObj, knownMods, modMode, fromIndex, indexSeqno){
280 | for (let mod of knownMods){
281 | slog.subtractSLog(wrappers[id].sLog, wrappers[mod].sLog);
282 | }
283 |
284 | if (wrappers[id].sLog.length < 1 || wrappers[id].sLog[0].data.seqno !== 1){//OP was deleted, nothing to do
285 | return;
286 | }
287 |
288 | let promises = [];
289 | for (let item of wrappers[id].sLog){
290 | if ((!modMode && item.removed) || !(item.data && item.data.payload && item.data.payload.data && item.data.payload.data.post)){
291 | continue;
292 | }
293 |
294 | if (document.getElementById(item.address)){
295 | //post already exists
296 | continue;
297 | }
298 |
299 | //TODO if (document.getElementById(item.address) && item.removed) - post deleted by a mod, grey it out
300 |
301 | let postDiv = document.createElement('div');
302 | postDiv.setAttribute('id', item.address);
303 | threadDiv.appendChild(postDiv);
304 |
305 | promises.push(
306 | slog.getSLogObj(item.data.payload.data.post).then(post => {
307 | fillPost(postDiv, post.data, item.data.seqno, item.data.timestamp, addressObj, fromIndex, indexSeqno, modMode, item.removed);
308 | threadDiv.insertBefore(document.createElement('br'), postDiv.nextSibling);
309 | return;
310 | })
311 | );
312 | }
313 |
314 | if (fromIndex) {
315 | threadDiv.appendChild(document.createElement('hr'));
316 | }
317 |
318 | return Promise.all(promises);
319 | }
320 |
321 | function loadBoard(id, wrappers, addressObj, knownMods, modMode){
322 | let threadStub = document.getElementById('thread');
323 |
324 | if (!wrappers[id].sLog) {
325 | return;
326 | }
327 |
328 | let promises = [];
329 | for (let item of wrappers[id].sLog){
330 | if ((!modMode && item.removed) || !(item.data && item.data.payload && item.data.payload.data && item.data.payload.data.thread)){
331 | continue;
332 | }
333 |
334 | let threadDiv = document.createElement('div');
335 | threadDiv.setAttribute('id', item.address);
336 | threadStub.appendChild(threadDiv);
337 |
338 | let threadId = item.data.payload.data.thread.address;
339 | promises.push(
340 | wrapperFuncs.pullWrapperStub(threadId, wrappers).then(() => {
341 | if (wrappers[threadId].last.data.info.mode !== 'thread'){
342 | return Promise.reject('Item ' + threadId + ' is not a valid thread.');
343 | } else {
344 | let promisesInner = [];
345 | promisesInner.push(slog.getSLogObj(wrappers[threadId].last.data.info.op));
346 | promisesInner.push(slog.updateSLog(wrappers[threadId].sLog, wrappers[threadId].last.data.head, 3));
347 | return Promise.all(promisesInner);
348 | }
349 | }).then(values => {
350 | let sLog = [];
351 | if (values[1][0].data.seqno !== 1){
352 | sLog.push(values[0]);
353 | }
354 | sLog = sLog.concat(values[1]);
355 | wrappers[threadId].sLog = sLog;
356 |
357 | return loadThread(threadDiv, threadId, wrappers, addressObj, knownMods, modMode, true, item.data.seqno);
358 | })
359 | );
360 | }
361 | return Promise.all(promises);
362 | }
363 |
364 | function loadThread(threadDiv, id, wrappers, addressObj, knownMods, modMode, fromIndex, indexSeqno){
365 | for (let mod of knownMods){
366 | slog.subtractSLog(wrappers[id].sLog, wrappers[mod].sLog);
367 | }
368 |
369 | if (wrappers[id].sLog.length < 1 || wrappers[id].sLog[0].data.seqno !== 1){//OP was deleted, nothing to do
370 | return;
371 | }
372 |
373 | if (threadDiv == null) {
374 | threadDiv = document.getElementById('thread');
375 | }
376 |
377 | let promises = [];
378 | for (let item of wrappers[id].sLog){
379 | if ((!modMode && item.removed) || !(item.data && item.data.payload && item.data.payload.data && item.data.payload.data.post)){
380 | continue;
381 | }
382 |
383 | if (document.getElementById(item.address)){
384 | //post already exists
385 | continue;
386 | }
387 |
388 | //TODO if (document.getElementById(item.address) && item.removed) - post deleted by a mod, grey it out
389 |
390 | let postDiv = document.createElement('div');
391 | postDiv.setAttribute('id', item.address);
392 | threadDiv.appendChild(postDiv);
393 |
394 | promises.push(
395 | slog.getSLogObj(item.data.payload.data.post).then(post => {
396 | fillPost(postDiv, post.data, item.data.seqno, item.data.timestamp, addressObj, fromIndex, indexSeqno, modMode, item.removed);
397 | threadDiv.insertBefore(document.createElement('br'), postDiv.nextSibling);
398 | return;
399 | })
400 | );
401 | }
402 |
403 | if (fromIndex) {
404 | threadDiv.appendChild(document.createElement('hr'));
405 | }
406 |
407 | return Promise.all(promises);
408 | }
409 |
410 | function setTemplateHTML(){
411 | let htmlHeadContent = pageHtml.css;
412 | htmlHeadContent += pageHtml.favicon;
413 | let htmlBodyContent = pageHtml.boardlist;
414 | //htmlBodyContent += pageHtml.loadIndicator;
415 | //htmlBodyContent += pageHtml.header
416 |
417 | document.head.innerHTML = htmlHeadContent;
418 | document.body.innerHTML = htmlBodyContent;
419 | }
420 |
421 | function setHeader(wrappers, addressObj){
422 | let title = null;
423 | let uri = null;
424 | let fromSite = false;
425 | for (let item of addressObj){
426 | if (item.mode === 'site'){
427 | title = wrappers[item.actualAddress].last.data.info.title;
428 | fromSite = true;
429 | }
430 | }
431 | for (let item of addressObj){
432 | if (item.mode === 'board'){
433 | title = wrappers[item.actualAddress].last.data.info.title;
434 | uri = item.publicAddress;
435 | fromSite = false;
436 | }
437 | }
438 | setTitle(title, uri, fromSite);
439 | }
440 |
441 | function setNavLinks(inThread, addressObj){
442 | if (inThread){
443 | document.getElementById('thread-return').setAttribute('href', functions.getNewHash(addressObj, ['thread', 'post']));
444 | }
445 | document.getElementById('catalog-link').setAttribute('href', functions.getNewHash(addressObj, ['post', 'thread', 'catalog'], ['catalog']));
446 | document.getElementById('index-link').setAttribute('href', functions.getNewHash(addressObj, ['post', 'thread', 'catalog']));
447 | }
448 |
449 | function setPostFormData(id){
450 | //if (item.mode === 'board' || item.mode === 'thread') {
451 | document.getElementById('submit').setAttribute('onclick', 'smug.submitPost("' + id + '", smug.wrappers, "postform")');
452 | }
453 |
454 | function setTitle(title, uri, fromSite){
455 | document.getElementById('heading').innerHTML = '';
456 | if (uri != null && title != null) {
457 | document.getElementById('heading').appendChild(document.createTextNode('/' + uri + '/ - ' + title));
458 | } else if (title != null) {
459 | if (fromSite) {
460 | document.getElementById('heading').appendChild(document.createTextNode(title));
461 | } else {
462 | document.getElementById('heading').appendChild(document.createTextNode('[Detached Board] - ' + title));
463 | }
464 | } else {
465 | document.getElementById('heading').appendChild(document.createTextNode('[Detached Thread]'));
466 | }
467 | }
468 |
469 | function setSiteHTML(){
470 | document.body.innerHTML += pageHtml.siteBoardlistHeader;
471 | document.body.innerHTML += pageHtml.siteBoardlist;
472 |
473 | //TODO hide load indicator
474 |
475 | }
476 |
477 | function setBoardHTML(){
478 | document.body.innerHTML += pageHtml.header;
479 | document.body.innerHTML += pageHtml.banner;
480 | document.body.innerHTML += pageHtml.uploadForm;
481 | document.body.innerHTML += pageHtml.threadStub;
482 | document.getElementById('submit').innerHTML = 'New Thread';
483 |
484 | }
485 |
486 | function setCatalogHTML(){
487 | document.body.innerHTML += pageHtml.header;
488 | document.body.innerHTML += pageHtml.banner;
489 | document.body.innerHTML += pageHtml.uploadForm;
490 | document.body.innerHTML += pageHtml.catalogStub;
491 | document.body.classList.value = 'theme-catalog active-catalog';
492 | document.getElementById('submit').innerHTML = 'New Thread';
493 | }
494 |
495 | function setThreadHTML(){
496 | document.body.innerHTML += pageHtml.header;
497 | document.body.innerHTML += pageHtml.banner;
498 | document.body.innerHTML += pageHtml.uploadForm;
499 | document.body.innerHTML += pageHtml.threadStub;
500 | document.body.innerHTML += pageHtml.threadControls;
501 |
502 | }
503 |
504 | function setEditorHTML(){
505 | document.body.innerHTML += pageHtml.objectEditorHeader;
506 | }
507 |
508 | function fillBoardlist(sitePublicAddress, siteSLog){
509 | document.getElementById('boardlist-home').setAttribute('href', '#' + sitePublicAddress);
510 | document.getElementById('boardlist-create').setAttribute('href', '#' + sitePublicAddress + '/create');
511 | document.getElementById('boardlist-options').setAttribute('href', '#' + sitePublicAddress + '/options');
512 | document.getElementById('boardlist-manage').setAttribute('href', '#' + sitePublicAddress + '/manage');
513 |
514 | let items = [];
515 | for (let entry of siteSLog){
516 | if (!entry.removed && entry.data && entry.data.payload && entry.data.payload.data && entry.data.payload.data.uri){
517 | items.push(entry.data.payload.data.uri);
518 | }
519 | }
520 |
521 | let firstBoard = true;
522 | let boards = document.getElementById('boardlist-boards');
523 | boards.innerHTML = ' [';
524 | for (let item of items){
525 | boards.innerHTML += firstBoard ? ' ' : ' / ';
526 | firstBoard = false;
527 |
528 | let link = document.createElement('a');
529 | link.setAttribute('href', '#' + sitePublicAddress + '/' + item);
530 | link.appendChild(document.createTextNode(item));
531 | boards.appendChild(link);
532 | }
533 | boards.innerHTML += ' ]';
534 | }
535 |
536 | function fillBoardlistTable(sitePublicAddress, id, wrappers){
537 | let boardTable = document.getElementById('boardlist-table');
538 |
539 | let headBoard = document.createElement('th');
540 | headBoard.appendChild(document.createTextNode('Board'));
541 | let headTitle = document.createElement('th');
542 | headTitle.appendChild(document.createTextNode('Title'));
543 | let headAddress = document.createElement('th');
544 | headAddress.appendChild(document.createTextNode('Address'));
545 |
546 | let headRow = document.createElement('tr');
547 | headRow.appendChild(headBoard);
548 | headRow.appendChild(headTitle);
549 | headRow.appendChild(headAddress);
550 | boardTable.appendChild(headRow);
551 |
552 | let payloads = [];
553 | for (let entry of wrappers[id].sLog){
554 | if (!entry.removed && entry.data && entry.data.payload && entry.data.payload.data && entry.data.payload.data.address){
555 | payloads.push(entry.data.payload);
556 | }
557 | }
558 |
559 | let promises = [];
560 | for (let payload of payloads){
561 | let uri = document.createElement('td');
562 | let title = document.createElement('td');
563 | let address = document.createElement('td');
564 |
565 | let link = document.createElement('a');
566 | link.setAttribute('href', '#' + sitePublicAddress + '/' + payload.data.uri);
567 | link.appendChild(document.createTextNode('/' + payload.data.uri + '/'));
568 | uri.appendChild(link);
569 |
570 | let rawLink = document.createElement('a');
571 | rawLink.setAttribute('href', '#' + payload.data.address);
572 | rawLink.appendChild(document.createTextNode(payload.data.address));
573 | address.appendChild(rawLink);
574 |
575 | let row = document.createElement('tr');
576 | row.appendChild(uri);
577 | row.appendChild(title);
578 | row.appendChild(address);
579 | boardTable.appendChild(row);
580 |
581 | promises.push(
582 | wrapperFuncs.pullWrapperStub(payload.data.address, wrappers).then(() => {
583 | title.appendChild(document.createTextNode(wrappers[payload.data.address].last.data.info.title));
584 | return;
585 | })
586 | );
587 | }
588 | return Promise.all(promises);
589 | }
590 | module.exports.setSiteHTML = setSiteHTML;
591 | module.exports.setBoardHTML = setBoardHTML;
592 | module.exports.setCatalogHTML = setCatalogHTML;
593 | module.exports.setThreadHTML = setThreadHTML;
594 | module.exports.setEditorHTML = setEditorHTML;
595 | module.exports.loadBoard = loadBoard;
596 | module.exports.loadCatalog = loadCatalog;
597 | module.exports.loadThread = loadThread;
598 | module.exports.setTemplateHTML = setTemplateHTML;
599 | module.exports.setHeader = setHeader;
600 | module.exports.setNavLinks = setNavLinks;
601 | module.exports.setPostFormData = setPostFormData;
602 | module.exports.setTitle = setTitle;
603 | module.exports.fillBoardlist = fillBoardlist;
604 | module.exports.fillBoardlistTable = fillBoardlistTable;
605 |
--------------------------------------------------------------------------------
/client/inc/pagehtml.js:
--------------------------------------------------------------------------------
1 | /* General HTML */
2 |
3 | var favicon = '';
4 |
5 | var css = '';
6 |
7 | var boardlist = '
';
8 |
9 | var loadIndicator = '
Loading...
';
10 |
11 | /* Thread HTML */
12 |
13 | var header = '
';
14 |
15 | var banner = 'Posting mode: Reply
';
16 |
17 | var uploadForm = '';
18 |
19 | var threadStub = '
';
20 |
21 | var threadControls = '
';
22 |
23 | /* Catalog HTML */
24 |
25 | var catalogStub = '';
26 |
27 | /* Site HTML (create page) */
28 |
29 | var siteModCreateHeader = '';
30 |
31 | var siteModCreateForm = '';
32 |
33 | var siteBoardCreateHeader = '';
34 |
35 | var siteBoardCreateForm = '';//TODO need to have way to put multiple things in each field
36 |
37 | var siteAddHeader = '';
38 |
39 | var siteAddForm = '';
40 |
41 | var settingsPage = '';
42 |
43 | /* Site HTML (Board list) */
44 |
45 | var siteBoardlistHeader = '';
46 |
47 | var siteBoardlist = '';
48 |
49 | /* Object editor */
50 |
51 | var objectEditorHeader = '';
52 |
53 | /* Mod stuff */
54 |
55 | var optionsHeader = '';
56 |
57 | var optionsForm = '';
58 |
59 | module.exports.favicon = favicon;
60 | module.exports.css = css;
61 | module.exports.boardlist = boardlist;
62 | module.exports.loadIndicator = loadIndicator;
63 | module.exports.header = header;
64 | module.exports.banner = banner;
65 | module.exports.uploadForm = uploadForm;
66 | module.exports.threadStub = threadStub;
67 | module.exports.catalogStub = catalogStub;
68 | module.exports.threadControls = threadControls;
69 | module.exports.siteModCreateHeader = siteModCreateHeader;
70 | module.exports.siteModCreateForm = siteModCreateForm;
71 | module.exports.siteBoardCreateHeader = siteBoardCreateHeader;
72 | module.exports.siteBoardCreateForm = siteBoardCreateForm;
73 | module.exports.siteAddHeader = siteAddHeader;
74 | module.exports.siteAddForm = siteAddForm;
75 | module.exports.settingsPage = settingsPage;
76 | module.exports.siteBoardlistHeader = siteBoardlistHeader;
77 | module.exports.siteBoardlist = siteBoardlist;
78 | module.exports.objectEditorHeader = objectEditorHeader;
79 | module.exports.optionsHeader = optionsHeader;
80 | module.exports.optionsForm = optionsForm;
81 |
82 |
83 |
--------------------------------------------------------------------------------
/client/inc/post.js:
--------------------------------------------------------------------------------
1 | var wrapperFuncs = require('../../common/wrapperfuncs.js');
2 | var util = require('../../common/util.js');
3 | //var sio = require('../../common/sio.js');
4 | var controller = require('./controller.js');
5 | var bridge = require('./serverbridge.js');
6 |
7 | function submitPost(wrapperId, wrappers, formName){
8 |
9 | //TODO grey out the button
10 | let formData = new FormData(document.getElementById(formName));
11 | let formKeys = formData.keys();
12 | let postData = {};
13 |
14 | for (let key of formKeys){
15 | postData[key] = formData.get(key);
16 | }
17 |
18 |
19 | if (postData.author === '') {
20 | postData.author = 'Anonymous';
21 | }
22 | postData.type = 'add';
23 |
24 | Promise.resolve().then(() => {
25 | if (!wrappers[wrapperId]){
26 | return wrapperFuncs.pullWrapperStub(wrapperId, wrappers);
27 | } else {
28 | return;
29 | }
30 | }).then(() => {
31 | //work out where we are
32 | if (!util.propertyExists(wrappers[wrapperId], 'last.data.info.mode')){
33 | Promise.reject('Not a valid object.');
34 | } else if (wrappers[wrapperId].last.data.info.mode === 'board'){
35 | //if we're at a board, we're creating a thread, posting to that thread, then posting that thread to a board server
36 | if (wrappers[wrapperId].last.data.info.threadservers && wrappers[wrapperId].last.data.info.threadservers.length > 0){
37 | //create a new thread
38 | let chosenServer = wrappers[wrapperId].last.data.info.threadservers[Math.floor(Math.random() * wrappers[wrapperId].last.data.info.threadservers.length)];
39 | let threadData = {
40 | mode: 'thread',
41 | type: 'create',
42 | id: chosenServer,
43 | server: chosenServer
44 | };
45 |
46 | return bridge.sendToServer(chosenServer, wrappers, threadData).then(threadAddress => {
47 | let promises = [];
48 | //post to that thread
49 | postData.mode = 'thread';
50 | postData.id = threadAddress;
51 | promises.push(bridge.sendToServer(threadAddress, wrappers, postData));
52 |
53 | //simultaneously, add that thread to the board in question
54 | let boardData = {
55 | mode: 'board',
56 | type: 'add',
57 | id: wrapperId,
58 | newaddress: threadAddress
59 | };
60 | promises.push(bridge.sendToServer(wrapperId, wrappers, boardData));
61 |
62 | Promise.all(promises).then(values => {
63 | return values;
64 | });
65 | });
66 | } else {
67 | Promise.reject('No server found.');
68 | }
69 | } else if (wrappers[wrapperId].last.data.info.mode === 'thread'){
70 | //if we're at a thread, we're posting to that thread's server.
71 | if (!wrappers[wrapperId].last.data.info.server){
72 | Promise.reject('No server found.');
73 | } else {
74 | postData.mode = wrappers[wrapperId].last.data.info.mode;
75 | postData.id = wrapperId;
76 | return bridge.sendToServer(wrappers[wrapperId].last.data.info.server, wrappers, postData).then(result => {
77 | controller.queueRefresh();
78 | return result;
79 | });
80 | }
81 | } else {
82 | Promise.reject('Can\'t post to that object');
83 | }
84 |
85 | //need to return the serverId we're using
86 | }).then(console.log).catch(console.error);
87 |
88 | //TODO ungrey the button
89 |
90 | //if we're at a board, we're posting to a thread server, then posting to a board server
91 | //send the post to the server at [actualAddress].last.data.info.threadservers, perhaps randomly selecting which
92 | //-> need to get the actual address of the thread server. it'd be nice if we already had it loaded
93 | //-> if a thread server fails, it might be good to go on to the next thread server? //yes, it'd be very good, almost a requirement in fact //TODO
94 | //create a new thread at that thread server
95 | //get back the address of the new thread then make the actual post to that thread
96 | //simultaneously, add the thread to the board server
97 | //once both are done, get back the assigned seqno, then add it to the parsed address stream, then refresh the page to there
98 |
99 | //if we're at a thread, we're posting to that thread server.
100 | //send the post to the server at [actualAddress].last.data.info.server
101 | //TODO get back the assigned seqno. if we have a 'post' in addressParsed, replace it. if we don't, add it to the address
102 | //TODO trigger page refresh, perhaps in ~3 seconds
103 | //TODO scroll to the post when the refresh is complete - which we should be tracking
104 | }
105 |
106 | module.exports.submitPost = submitPost;
107 |
--------------------------------------------------------------------------------
/client/inc/serverbridge.js:
--------------------------------------------------------------------------------
1 | var wrapperFuncs = require('../../common/wrapperfuncs.js');
2 | var inputHandler = require('../../common/inputhandler.js');
3 |
4 | //TODO track local servers
5 | var localWrappers = {};//should this perhaps just be integrated into the standard wrapper construct? perhaps with wrappers[id].local == true ?
6 |
7 | function isLocalServer(serverId){
8 | if (localWrappers[serverId] != null){
9 | return true;
10 | } else {
11 | return false;
12 | }
13 | }
14 |
15 | function sendToRemoteServer(serverId, wrappers, formData){
16 | return new Promise((resolve, reject) => {
17 | wrapperFuncs.getServerConn(serverId, wrappers).then(conn => {//TODO just return this?
18 | let req = new XMLHttpRequest();//TODO try transitioning this to the xhr library, don't know if doable with files
19 | req.onreadystatechange = () => {
20 | if (req.readyState === XMLHttpRequest.DONE) {
21 | resolve(req.responseText);//TODO should resolve an obj with the same format as from inputHandler
22 | }
23 | };
24 | req.addEventListener('error', err => {
25 | reject(err);
26 | });
27 | req.open('POST', conn.ip);
28 | req.send(formData);
29 | }).catch(reject);
30 | });
31 | }
32 |
33 | function sendToLocalServer(){
34 | //TODO
35 | let reader = new FileReader();
36 | return inputHandler.handleInput(localWrappers, msg, file, remoteAddress, serverAddress);
37 | }
38 |
39 | function objToFormData(obj){//TODO move to functions
40 | let formData = new FormData();
41 | for (let item in obj){
42 | formData.set(item, obj[item]);
43 | }
44 | return formData;
45 | }
46 |
47 | function sendToServer(serverId, wrappers, formObj){
48 | //determine if the server is local to the current browser
49 | if (isLocalServer(serverId)){
50 | //if so, send to the local server
51 | return sendToLocalServer();
52 | } else {
53 | //if not, send to the remote server
54 | let formData = objToFormData(formObj);
55 | return sendToRemoteServer(serverId, wrappers, formData);
56 | }
57 | }
58 |
59 | module.exports.sendToServer = sendToServer;
60 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 | If the page gets stuck here, you probably haven't configured IPFS correctly. Go have a look at the instructions and try again.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/client/smugchan.js:
--------------------------------------------------------------------------------
1 | var functions = require('./inc/functions.js');
2 | //var pageHtml = require('./inc/pagehtml.js');
3 | var page = require('./inc/page.js');
4 | var post = require('./inc/post.js');
5 | var controller = require('./inc/controller.js');
6 | //var sio = require('../common/sio.js');
7 |
8 | var globalWrappers = {};
9 | var addressParsed = [];
10 |
11 | function loadPage(){
12 | controller.haltRefresh();
13 |
14 | var newAddressParsed = functions.parseLocationAddress(location.hash);
15 |
16 | var isNewPage = false;
17 | if (addressParsed === []) {
18 | isNewPage = true;
19 | //TODO work out if we're on the same page
20 | //if the last item was a thread, and we've just added an extra item, then it's the same page
21 | //if the last item was a post, and we've just changed the item at that position, then it's the same page
22 | //otherwise it's a different page
23 | }
24 | isNewPage = true;
25 |
26 | if (isNewPage){
27 | //globalWrappers = {};//TODO get rid of this?
28 |
29 | page.setTemplateHTML();
30 |
31 | window.smug.addressObj = addressParsed = newAddressParsed;
32 | window.smug.knownMods = [];
33 |
34 |
35 | controller.handleWrapperRecursive(addressParsed, globalWrappers, 0, false, window.smug.knownMods);
36 | } else {
37 | //TODO scroll to element
38 | }
39 | }
40 |
41 | document.addEventListener('DOMContentLoaded', () => {
42 | var smug = window.smug || {};
43 | smug.submitPost = post.submitPost;
44 | smug.wrappers = globalWrappers;
45 | smug.addressObj = addressParsed;
46 | smug.forceRefresh = controller.forceRefresh;
47 | window.smug = smug;
48 | loadPage();
49 | }, false);
50 |
51 | window.addEventListener('hashchange', loadPage, false);
52 |
--------------------------------------------------------------------------------
/client/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #eef2ff url("img/fade-blue.png") repeat-x scroll 50% 0;
3 | color: black;
4 | font-family: arial,helvetica,sans-serif;
5 | font-size: 10pt;
6 | margin: 0 4px;
7 | padding-left: 4px;
8 | padding-right: 4px;
9 | padding-top: 20px;
10 | }
11 |
12 | img.banner, img.board_image {
13 | border: 1px solid #a9a9a9;
14 | display: block;
15 | margin: 12px auto 0;
16 | }
17 |
18 | header {
19 | margin: 1em 0;
20 | }
21 |
22 | header div.subtitle, h1 {
23 | color: #af0a0f;
24 | text-align: center;
25 | }
26 | header div.subtitle {
27 | font-size: 8pt;
28 | }
29 |
30 | h1 {
31 | font-family: tahoma;
32 | font-size: 20pt;
33 | letter-spacing: -2px;
34 | margin: 0;
35 | }
36 |
37 | div.banner, div.banner a {
38 | color: white;
39 | }
40 |
41 | div.banner {
42 | background-color: #e04000;
43 | font-size: 12pt;
44 | font-weight: bold;
45 | margin: 1em 0;
46 | text-align: center;
47 | }
48 |
49 |
50 | .boardlist-table {
51 | display: table;
52 | margin: -2px;
53 | margin-bottom: 10px;
54 | overflow: hidden;
55 | table-layout: fixed;
56 | }
57 |
58 | #boardlist-table {
59 | /*width: 100%;*/
60 | margin-left:auto;
61 | margin-right:auto;
62 | }
63 |
64 | #boardlist-table td {
65 | margin: 0;
66 | padding: 4px 15px 4px 4px;
67 |
68 | text-align: left;
69 | }
70 | #boardlist-table th {
71 | border: 1px solid #000333;
72 | padding: 4px 15px 5px 5px;
73 |
74 | background: #98E;
75 | color: #000333;
76 | text-align: left;
77 | white-space: nowrap;
78 | }
79 |
80 |
81 | #post-form-outer {
82 | text-align: center;
83 | }
84 |
85 | #post-form-inner {
86 | display: inline-block;
87 | }
88 |
89 | .post-table, .post-table-options, textarea {
90 | width: 100%;
91 | }
92 |
93 | form table {
94 | margin: auto;
95 | font-size: 10pt;
96 | }
97 |
98 | #post-form-inner .post-table tr {
99 | background-color: transparent;
100 | }
101 |
102 | .post-table th, .post-table-options th {
103 | width: 85px;
104 | }
105 |
106 | form table tr th {
107 | background: #98e none repeat scroll 0 0;
108 | }
109 |
110 | form table tr th {
111 | padding: 4px;
112 | text-align: left;
113 | }
114 |
115 | form table tr td {
116 | margin: 0;
117 | padding: 0;
118 | text-align: left;
119 | }
120 |
121 | input[type="text"], input[type="password"], textarea {
122 | border: 1px solid #a9a9a9;
123 | font-family: sans-serif;
124 | font-size: inherit;
125 | text-indent: 0;
126 | text-shadow: none;
127 | text-transform: none;
128 | word-spacing: normal;
129 | }
130 |
131 | form table input {
132 | height: auto;
133 | }
134 |
135 | .required-star {
136 | color: maroon;
137 | }
138 |
139 | hr {
140 | -moz-border-bottom-colors: none;
141 | -moz-border-left-colors: none;
142 | -moz-border-right-colors: none;
143 | -moz-border-top-colors: none;
144 | border-color: #b7c5d9;
145 | border-image: none;
146 | border-style: solid none none;
147 | border-width: 1px medium medium;
148 | clear: left;
149 | height: 0;
150 | }
151 |
152 | div.post.reply div.body a {
153 | color: #D00;
154 | }
155 |
156 | a, a:visited {
157 | color: #34345c;
158 | text-decoration: underline;
159 | }
160 |
161 | a.post_no {
162 | margin: 0;
163 | padding: 0;
164 | text-decoration: none;
165 | }
166 |
167 | /* Board List */
168 | div.boardlist {
169 | margin-top: 3px;
170 |
171 | color: #89A;
172 | font-size: 9pt;
173 | }
174 | div.boardlist.bottom {
175 | margin-top: 12px;
176 | clear: both;
177 | }
178 | div.boardlist a {
179 | text-decoration: none;
180 | }
181 |
182 | div.boardlist:not(.bottom) {
183 | position: fixed;
184 | top: 0;
185 | left: 0;
186 | right: 0;
187 | margin-top: 0;
188 | z-index: 30;
189 | box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
190 | border-bottom: 1px solid;
191 | background-color: #D6DAF0;
192 | }
193 |
194 |
195 | div.post.op {
196 | margin-bottom: 5px;
197 | margin-right: 20px;
198 | }
199 |
200 | div.post.reply.flagged {
201 | background: #a1a0ab;
202 | }
203 |
204 | div.post.reply {
205 | background: #d6daf0 none repeat scroll 0 0;
206 | border-color: #b7c5d9;
207 | border-style: none solid solid none;
208 | border-width: 1px;
209 | display: inline-block;
210 | margin: 0.2em 4px;
211 | max-width: 94% !important;
212 | padding: 0.5em 0.3em 0.5em 0.6em;
213 | }
214 |
215 | div.post .post-image {
216 | padding: 5px;
217 | margin: 0 20px 0 0;
218 | }
219 |
220 | .post-image {
221 | display: block;
222 | float: left;
223 | border: none;
224 | }
225 |
226 | div.post p.fileinfo {
227 | padding-left: 5px;
228 | }
229 |
230 | div.post.reply div.body {
231 | margin-left: 1.8em;
232 | }
233 |
234 | div.post.reply {
235 | min-width: 33%;
236 | }
237 |
238 | div.post div.body {
239 | white-space: pre-wrap;
240 | word-wrap: break-word;
241 | }
242 |
243 | div.post div.body {
244 | margin-top: 0.8em;
245 | padding-bottom: 0.3em;
246 | padding-right: 3em;
247 | }
248 |
249 | div.post p {
250 | display: block;
251 | margin: 0;
252 | line-height: 1.16em;
253 | font-size: 13px;
254 | min-height: 1.16em;
255 | }
256 |
257 | .unimportant, .unimportant * {
258 | font-size: 10px;
259 | }
260 |
261 | .intro {
262 | margin-left: 10px;
263 | padding: 0 0 0.2em;
264 | }
265 |
266 | .intro label {
267 | display: inline;
268 | }
269 |
270 | .intro a.post_no {
271 | color: inherit;
272 | }
273 |
274 | .intro span, .intro a.email {
275 | margin-right: 5px;
276 | }
277 |
278 | .intro span.subject, .intro .reply_link {
279 | margin-left: 5px;
280 | }
281 |
282 | .intro span.subject, .intro a.email {
283 | color: #0f0c5d;
284 | font-weight: bold;
285 | }
286 |
287 | .intro span.name {
288 | color: #117743;
289 | font-weight: bold;
290 | }
291 |
292 | span.heading {
293 | color: #AF0A0F;
294 | font-size: 11pt;
295 | font-weight: bold;
296 | }
297 |
298 | span.spoiler:hover,div.post.reply div.body span.spoiler:hover a {
299 | color: white;
300 | }
301 |
302 | span.spoiler {
303 | background: black;
304 | color: black;
305 | padding: 0 1px;
306 | }
307 |
308 | span.controls {
309 | float: right;
310 | margin: 0;
311 | padding: 0;
312 | font-size: 80%;
313 | }
314 |
315 | span.controls.op {
316 | float: none;
317 | margin-left: 10px;
318 | }
319 |
320 | span.controls a {
321 | margin: 0;
322 | }
323 |
324 | #thread-interactions {
325 | clear: both;
326 | margin: 8px 0;
327 | }
328 |
329 | #thread-links {
330 | float: left;
331 | }
332 |
333 | #thread-links > a {
334 | padding-right: 10px;
335 | }
336 |
337 |
338 | .theme-catalog div.thread img {
339 | float: none!important;
340 | margin: auto;
341 | max-height: 150px;
342 | max-width: 200px;
343 | box-shadow: 0 0 4px rgba(0,0,0,0.55);
344 | border: 2px solid rgba(153,153,153,0);
345 | }
346 |
347 | .theme-catalog div.thread {
348 | display: inline-block;
349 | vertical-align: top;
350 | text-align: center;
351 | font-weight: normal;
352 | margin-top: 2px;
353 | margin-bottom: 2px;
354 | padding: 2px;
355 | height: 300px;
356 | width: 205px;
357 | overflow: hidden;
358 | position: relative;
359 | font-size: 11px;
360 | max-height: 300px;
361 | background: rgba(182, 182, 182, 0.12);
362 | border: 2px solid rgba(111, 111, 111, 0.34);
363 | max-height:300px;
364 | }
365 |
366 | .theme-catalog div.thread strong {
367 | display: block;
368 | }
369 |
370 | .theme-catalog div.threads {
371 | text-align: center;
372 | margin-left: -20px;
373 | }
374 |
375 | .theme-catalog div.thread:hover {
376 | background: #D6DAF0;
377 | border-color: #B7C5D9;
378 | }
379 |
380 | .theme-catalog div.grid-size-vsmall img {
381 | max-height: 33%;
382 | max-width: 95%
383 | }
384 |
385 | .theme-catalog div.grid-size-vsmall {
386 | min-width:90px; max-width: 90px;
387 | max-height: 148px;
388 | }
389 |
390 | .theme-catalog div.grid-size-small img {
391 | max-height: 33%;
392 | max-width: 95%
393 | }
394 |
395 | .theme-catalog div.grid-size-small {
396 | min-width:140px; max-width: 140px;
397 | max-height: 192px;
398 | }
399 |
400 | .theme-catalog div.grid-size-medium img {
401 | max-height: 33%;
402 | max-width: 95%
403 | }
404 |
405 | .theme-catalog div.grid-size-medium {
406 | min-width:200px; max-width: 200px;
407 | max-height: 274px;
408 | }
409 |
410 | .theme-catalog div.grid-size-large img {
411 | max-height: 40%;
412 | max-width: 95%
413 | }
414 |
415 | .theme-catalog div.grid-size-large {
416 | min-width: 256px; max-width: 256px;
417 | max-height: 384px;
418 | }
419 |
420 | .theme-catalog img.thread-image {
421 | height: auto;
422 | max-width: 100%;
423 | }
424 |
425 | @media (max-width: 420px) {
426 | .theme-catalog ul#Grid {
427 | padding-left: 18px;
428 | }
429 |
430 | .theme-catalog div.thread {
431 | width: auto;
432 | margin-left: 0;
433 | margin-right: 0;
434 | }
435 |
436 | .theme-catalog div.threads {
437 | overflow: hidden;
438 | }
439 | div.post .body {
440 | clear: both;
441 | }
442 | }
443 |
444 |
--------------------------------------------------------------------------------
/common/inputhandler.js:
--------------------------------------------------------------------------------
1 | var sio = require('./sio.js');
2 | var storage = require('./storage.js');
3 | var util = require('./util.js');
4 | var wrapperFuncs = require('./wrapperfuncs.js');
5 |
6 | var serverModes = ['site', 'board', 'thread', 'mod', 'server'];
7 |
8 | var formats = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'webm', 'mp4', 'mp3', 'ogg', 'opus', 'flac', 'apng', 'pdf', 'iso', 'zip', 'tar', 'gz', 'rar', '7z', 'torrent'];
9 | var previewable = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico'];//things image-size can deal with
10 |
11 | function isValidRequest(mode, type, password, key, wrappers){
12 | let requestValid = false;
13 | let authorized = false;
14 | if (key != null && wrappers[key] != null && wrappers[key].password === password) {
15 | authorized = true;
16 | }
17 |
18 | //authorized = true; //TODO remove this, for test purposes only
19 |
20 | if (mode == null || type == null || serverModes.indexOf(mode) === -1){
21 | return false;
22 | } else if (type === 'create') {
23 | requestValid = true;
24 | } else if (type === 'add' && (mode !== 'mod' || authorized)) {
25 | requestValid = true;
26 | } else if (type === 'remove' && authorized) {
27 | requestValid = true;
28 | } else if (type === 'set' && authorized) {
29 | requestValid = true;
30 | } else if (type === 'delete' && authorized) {
31 | requestValid = true;
32 | }
33 |
34 | return requestValid;
35 | }
36 |
37 | //handler (generic)
38 | function handleInput(wrappers, msg, files, sender, serverAddress){
39 | return new Promise((resolve, reject) => {
40 | if (isValidRequest(msg.mode, msg.type, msg.password, msg.id, wrappers)){
41 | let wrapperOperation;
42 | switch(msg.type){
43 | case 'create':
44 | wrapperOperation = wrapperFuncs.createObj(msg, wrappers, serverAddress);
45 | break;
46 | case 'add':
47 | wrapperOperation = wrapperFuncs.addToObj(msg, wrappers, files, formats, previewable);
48 | break;
49 | case 'remove':
50 | wrapperOperation = wrapperFuncs.removeFromObj(msg, wrappers);
51 | break;
52 | case 'set':
53 | wrapperOperation = wrapperFuncs.setObj(msg, wrappers, serverAddress);
54 | break;
55 | case 'delete':
56 | wrapperOperation = wrapperFuncs.deleteObj(msg, wrappers);
57 | }
58 |
59 | wrapperOperation.then(resp => {
60 | storage.saveWrappers(util.prepareStorageKeys(wrappers)); //TODO put this somewhere else
61 | resolve(resp);
62 | }).catch(reject);
63 | } else {
64 | reject({status: 400, result: 'Invalid request or password.'});
65 | }
66 | });
67 | }
68 |
69 | function setupServer(wrappers){
70 | return Promise.all([storage.getWrappers(), sio.getKeys()]).then((values) => {
71 | wrappers = util.rebuildKeys(values[0], values[1]);
72 |
73 | /*console.log('Test point A');
74 | console.log(wrappers);
75 | let board;
76 | handleInput({mode: 'board', type: 'create', password: 'fugg', title: 'My Shitpoos', formats: ['png', 'jpg'], mods: []}, [null], 'hurr', 'http://localhost').then(obj => {
77 | console.log('Test point B');
78 | console.log(obj);
79 | board = obj.result;
80 | console.log(wrappers);
81 | return handleInput({mode: 'thread', type: 'create', password: 'fugg', title: 'My Shitthread'}, [null], 'hurr', 'http://localhost');
82 | }).then(obj => {
83 | console.log('Test point C');
84 | console.log(obj);
85 | console.log(wrappers);
86 | return handleInput({mode: 'board', type: 'add', id: board, password: 'fugg', newaddress: obj.result}, [null], 'hurr', 'http://localhost');
87 | }).then(obj => {
88 | console.log('Test point D');
89 | console.log(obj);
90 | console.log(wrappers);
91 | return handleInput({mode: 'board', type: 'add', id: board, password: 'fugg', newaddress: obj.result}, [null], 'hurr', 'http://localhost');
92 | }).then(obj => {
93 | console.log('Test point E');
94 | console.log(obj);
95 | console.log(wrappers);
96 | return handleInput({mode: 'board', type: 'add', id: board, password: 'fugg', newaddress: obj.result}, [null], 'hurr', 'http://localhost');
97 | }).then(obj => {
98 | console.log('Test point F');
99 | console.log(obj);
100 | console.log(wrappers);
101 | }).catch(console.error);*/
102 |
103 | /*var server;
104 | var mod;
105 | var site;
106 | var board;
107 | var thread;
108 | var post3;
109 | handleInput({mode: 'server', type: 'create', password: 'fugg', title: 'Server-chan'}, [null], 'hurr', 'http://localhost:3010').then(obj => {
110 | server = obj.result;
111 | console.log('Server: ' + server);
112 | return handleInput({mode: 'mod', type: 'create', password: 'fugg', title: 'Pockets the Mod', server: server}, [null], 'hurr');
113 | }).then(obj => {
114 | mod = obj.result;
115 | console.log('Mod: ' + mod);
116 | return handleInput({mode: 'site', type: 'create', password: 'fugg', title: 'Smugchan NEXT', formats: ['png', 'jpg'], mods: [mod], server: server}, [null], 'hurr');
117 | }).then(obj => {
118 | site = obj.result;
119 | console.log('Site: ' + site);
120 | return handleInput({mode: 'thread', type: 'create', password: 'fugg', title: 'My Shitthread', server: server}, [null], 'hurr');
121 | }).then(obj => {
122 | thread = obj.result;
123 | console.log('Thread: ' + thread);
124 | return handleInput({mode: 'board', type: 'create', password: 'fugg', title: 'Random', formats: ['png', 'jpg'], mods: [mod], threadservers: [server], server: server}, [null], 'hurr');
125 | }).then(obj => {
126 | board = obj.result;
127 | console.log('Board: ' + board);
128 | return handleInput({mode: 'site', type: 'add', id: site, password: 'fugg', newaddress: board, uri: 'b'}, [null], 'hurr');
129 | }).then(obj => {
130 | console.log('Board added to site at: ' + obj.result);
131 | return handleInput({mode: 'board', type: 'add', id: board, password: 'fugg', newaddress: thread}, [null], 'hurr');
132 | }).then(obj => {
133 | console.log('Thread added to board at: ' + obj.result);
134 | return handleInput({mode: 'thread', type: 'add', id: thread, password: 'fugg', author: 'Anonymous', email: 'sage', body: 'Babbies first shitpoast'}, [null], 'hurr');
135 | }).then(obj => {
136 | console.log('Post added to thread at: ' + obj.result);
137 | return handleInput({mode: 'thread', type: 'add', id: thread, password: 'fugg', author: 'Anonymous', body: 'Babbies second shitpoast'}, [null], 'hurr');
138 | }).then(obj => {
139 | console.log('Post added to thread at: ' + obj.result);
140 | return handleInput({mode: 'thread', type: 'add', id: thread, password: 'fugg', author: 'Anonymous', body: 'Babbies 3rd shitpoast'}, [null], 'hurr');
141 | }).then(obj => {
142 | console.log('Post added to thread at: ' + obj.result);
143 | return handleInput({mode: 'thread', type: 'add', id: thread, password: 'fugg', author: 'Anonymous', body: 'Babbies 4th shitpoast'}, [null], 'hurr');
144 | }).then(obj => {
145 | console.log('Post added to thread at: ' + obj.result);
146 | //return handleInput({mode: 'mod', type: 'add', id: mod, password: 'fugg', operation: 'remove', target: '/ipfs/Qmaddress'}, [null], 'hurr');
147 | }).catch(console.error);*/
148 |
149 |
150 | return;
151 | });
152 | }
153 |
154 | module.exports.handleInput = handleInput;
155 | module.exports.setupServer = setupServer;
156 |
157 |
--------------------------------------------------------------------------------
/common/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "common",
3 | "version": "0.0.1",
4 | "description": "",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "browser": {
9 | "request": "xhr",
10 | "image-size": "xhr",
11 | "./storage.js": "./storagebrowser.js"
12 | },
13 | "author": "smugdev",
14 | "license": "MIT",
15 | "dependencies": {
16 | "image-size": "^0.6.1",
17 | "ipfs-api": "^14.3.5",
18 | "request": "^2.81.0",
19 | "xhr": "^2.4.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/common/payloads.js:
--------------------------------------------------------------------------------
1 | var sio = require('./sio.js');
2 | var imgDims = require('image-size');//TODO presently this package doesn't work with browserify - good luck fixing that one
3 | //TODO consider instead making the client work out the dimensions
4 |
5 | function processSingleFile(fileInfo, formats, previewable){
6 | return new Promise((resolve, reject) => {
7 | console.log(fileInfo);
8 | if (fileInfo != null && fileInfo.buffer != null){
9 | let file = {};
10 | file.filename = fileInfo.originalname;
11 | let filenameSplit = fileInfo.originalname.split('.');
12 | file.extension = filenameSplit[filenameSplit.length - 1];
13 | file.size = fileInfo.size;
14 |
15 | if (formats.indexOf(file.extension) !== -1){
16 | sio.ipfsAddBuffer(fileInfo.buffer).then(fileHash => {
17 | return sio.ipfsNameObject(fileHash, fileInfo.originalname);
18 | }).then(fileAddress => {
19 | file.address = fileAddress;
20 |
21 | let fileDims = null;
22 | if (previewable.indexOf(file.extension) !== -1){
23 | try {
24 | fileDims = imgDims(fileInfo.buffer);
25 | } catch (err){
26 | reject('Getting image dims failed: ' + err);
27 | }
28 | }
29 | if (fileDims != null){
30 | file.height = fileDims.height;
31 | file.width = fileDims.width;
32 | }
33 |
34 | resolve(file);
35 | }).catch(reject);
36 | } else {
37 | reject('Invalid filetype.');
38 | }
39 | } else {
40 | reject('Invalid file data.');
41 | }
42 | });
43 | }
44 |
45 | function processFiles(filesInfo, formats, previewable){
46 | return new Promise((resolve, reject) => {
47 | let fileProcessors = [];
48 | for (let fileInfo of filesInfo){
49 | if (fileInfo != null){
50 | fileProcessors.push(processSingleFile(fileInfo, formats, previewable));
51 | }
52 | }
53 | Promise.all(fileProcessors).then(values => { //Promise.all is so fucking cool, I should've used this a long time ago
54 | resolve(values);
55 | }).catch(reject);
56 | });
57 | }
58 |
59 | function getThreadPayload(msg, filesInfo, formats, previewable){
60 | return new Promise((resolve, reject) => {
61 | let payload = {author: '', email: '', subject: '', body: ''};
62 | if (msg.author) {
63 | payload.author = msg.author;
64 | }
65 | if (msg.email) {
66 | payload.email = msg.email;
67 | }
68 | if (msg.subject) {
69 | payload.subject = msg.subject;
70 | }
71 | if (msg.body) {
72 | payload.body = msg.body;
73 | }
74 |
75 | processFiles(filesInfo, formats, previewable).then(files => {
76 | payload.files = files;
77 | return sio.ipfsAddObject(payload);
78 | }).then(payloadAddress => {
79 | resolve({data: {post: {address: payloadAddress}}});
80 | }).catch(reject);
81 | });
82 | }
83 |
84 | function getBoardPayload(msg){
85 | return new Promise((resolve, reject) => {
86 | let payload = {data: {thread: {address: ''}}};
87 | if (msg.newaddress) {
88 | payload.data.thread.address = msg.newaddress;
89 | } //TODO verify this is a valid IPNS name, and perhaps that it points to a valid thread wrapper? or doing it client side would work too probably
90 |
91 | resolve(payload);
92 | //sio.ipfsAddObject(payload).then(payloadAddress => {
93 | //resolve(payloadAddress);
94 | //}).catch(reject);
95 | });
96 | }
97 |
98 | function getSitePayload(msg){
99 | return new Promise((resolve, reject) => {
100 | let payload = {data: {}};
101 | if (msg.newaddress) {
102 | payload.data.address = msg.newaddress;
103 | } else {
104 | reject('Missing address of board to attach.');
105 | }
106 |
107 | if (msg.uri) {
108 | payload.data.uri = msg.uri;
109 | } else {
110 | reject('Missing uri to assign board to.');
111 | }
112 |
113 | resolve(payload);
114 | //sio.ipfsAddObject(payload).then(payloadAddress => {
115 | // resolve(payloadAddress);
116 | //}).catch(reject);
117 | });
118 | }
119 |
120 | function getModPayload(msg){
121 | return new Promise((resolve, reject) => {
122 | let payload = {data: {}};
123 | if (msg.target) {
124 | payload.data.target = msg.target;
125 | } else {
126 | reject('Missing address of post to apply action to.');
127 | }
128 |
129 | if (msg.operation) {
130 | payload.data.operation = msg.operation;
131 | } else {
132 | reject('Missing operation type.');
133 | }
134 |
135 | if (msg.content) {
136 | payload.data.content = msg.content;
137 | }
138 |
139 | resolve(payload);
140 | //sio.ipfsAddBuffer(JSON.stringify(payload)).then(payloadAddress => {
141 | // resolve(payloadAddress);
142 | //}).catch(reject);
143 | });
144 | }
145 |
146 |
147 | module.exports.getThreadPayload = getThreadPayload;
148 | module.exports.getBoardPayload = getBoardPayload;
149 | module.exports.getSitePayload = getSitePayload;
150 | module.exports.getModPayload = getModPayload;
151 |
--------------------------------------------------------------------------------
/common/settings.js:
--------------------------------------------------------------------------------
1 | /* Server public IP address */
2 | module.exports.serverAddress = 'http://localhost';
3 |
4 | /* Server default password (temporary arrangement) */
5 | module.exports.password = 'myverysecurepassword';
6 |
7 |
--------------------------------------------------------------------------------
/common/sio.js:
--------------------------------------------------------------------------------
1 | var request = require('request');
2 | var ipfsAPI = require('ipfs-api');
3 | var ipfs = ipfsAPI('localhost', '5001', {protocol: 'http'});
4 |
5 | var nameservers = [];
6 | nameservers.push(require('./settings.js').serverAddress + ':3005');
7 | var sitePassword = require('./settings.js').password;
8 |
9 | var util = require('./util.js');
10 |
11 | function genKey(keyName){
12 | return ipfs.key.gen(keyName, {type: 'rsa', size: '2048'});
13 | }
14 |
15 | function getKeys(){
16 | return ipfs.key.list();
17 | }
18 |
19 | function ipfsAddBuffer(buffer){
20 | return ipfs.util.addFromStream(buffer).then(result => {
21 | return result[result.length - 1].hash;
22 | });
23 | }
24 |
25 | function ipfsAddObject(object){
26 | return ipfsAddBuffer(Buffer.from(JSON.stringify(object), 'utf8')).then(hash => {
27 | return '/ipfs/' + hash;
28 | });
29 | }
30 |
31 | function ipfsNameObject(hash, filename){
32 | return ipfs.object.new('unixfs-dir').then(node => {
33 | return ipfs.object.patch.addLink(node.multihash, {
34 | name: filename,
35 | multihash: hash
36 | });
37 | }).then(result => {
38 | return '/ipfs/' + result._json.multihash + '/' + filename;
39 | });
40 | }
41 | //ipfs.object.new('unixfs-dir').then(node => {return ipfs.object.patch.addLink(node.multihash, {name: 'hurr.jpg', multihash: 'QmfT7z9atYSk1ymQjMMBtDbDv8prj67roAkFzn8MAeT2BA'})}).then(console.log).catch(console.error)
42 |
43 | function ipfsPublishToKey(address, keyName){
44 | return ipfs.name.publish(address, {key: keyName});
45 | }
46 |
47 | function publishToNameservers(id, association, password){
48 | return new Promise((resolve, reject) => {
49 | let promises = [];
50 | let jsonObj = {
51 | address: id,
52 | association: association,
53 | password: password
54 | };
55 | for (let server of nameservers){
56 | promises.push(sendPost(server, jsonObj));
57 | }
58 | Promise.all(promises).then(() => {
59 | resolve('Published to all name servers.');
60 | }).catch(reject);
61 | });
62 | }
63 |
64 | function ipfsPublish(address, keyName, pubkey){
65 | console.log('Publishing ' + address + ' to ' + keyName + ' (' + pubkey + ')');
66 |
67 | publishToNameservers(pubkey, address, sitePassword).then(resp => console.log).catch(err => console.error);
68 |
69 | ipfsPublishToKey(address, keyName).then(resp => console.log).catch(err => console.error);
70 | }
71 |
72 | function resolveFromSingleNameserver(nameserver, address){
73 | return new Promise((resolve, reject) => {
74 | let requestAddress = nameserver + '?address=' + address;
75 | loadAddress(requestAddress).then(resolve).catch(reject);
76 | });
77 | }
78 |
79 | function resolveFromNameservers(nameservers, address){
80 | return new Promise((resolve, reject) => {
81 | if (nameservers.length < 1) {
82 | reject('No nameservers.');
83 | }
84 | let promises = [];
85 | for (let nameserver of nameservers){
86 | promises.push(resolveFromSingleNameserver(nameserver, address).then(resolve));
87 | }
88 | Promise.all(promises).catch(reject);
89 | // TODO The logic is supposed to be be "return the first nameserver that resolves, and if none of them do then reject" - need to check the impl. here is right
90 | // TODO it's not right, promise.all will reject as soon as one of these fails. Fuck. Why can't Promise.race do normal shit
91 | });
92 | }
93 |
94 | function ipfsResolveName(address){
95 | return new Promise((resolve, reject) => {
96 | ipfs.name.resolve(address, (err, resolvedName) => {
97 | if (err) {
98 | reject(err);
99 | }
100 | resolve(resolvedName.Path);
101 | });
102 | });
103 | }
104 |
105 | function resolveName(address){
106 | return new Promise((resolve, reject) => {
107 | var addressSplit = address.split('/');// TODO ensure we can assume that the addresses will already be correct
108 | var addressActual = '/' + addressSplit[addressSplit.length - 2] + '/' + addressSplit[addressSplit.length - 1];
109 | if (addressSplit[addressSplit.length - 2] === 'ipns'){
110 | resolveFromNameservers(nameservers, addressActual).then(resolve).catch(err => {
111 | console.error(err);
112 | ipfsResolveName(addressActual).then(resolve).catch(reject);
113 | });
114 | } else {
115 | resolve(addressActual);
116 | }
117 | });
118 | }
119 |
120 | function ipfsGetJsonObject(address){
121 | return new Promise((resolve, reject) => {
122 | ipfs.files.cat(util.hashFromAddress(address)).then(stream => {
123 | let result = '';
124 | stream.on('data', (chunk) => {
125 | result += chunk;
126 | });
127 | stream.on('end', () => {
128 | resolve(JSON.parse(result));
129 | });
130 | }).catch(reject);
131 | });
132 | }
133 |
134 | function getJsonObject(address){
135 | return resolveName(address).then(addressActual => {
136 | return ipfsGetJsonObject(addressActual);
137 | });
138 | }
139 |
140 | function loadAddress(address){
141 | return new Promise((resolve, reject) => {
142 | request({
143 | uri: address,
144 | headers: {
145 | 'Content-Type': 'application/json'
146 | }
147 | }, (err, resp, body) => {
148 | if (resp != null && resp.statusCode === 200){
149 | if (resp.body != null){
150 | resolve(resp.body);
151 | } else {
152 | reject('Invalid response: ' + resp);
153 | }
154 | } else {
155 | reject(err);
156 | }
157 | });
158 | });
159 |
160 | }
161 |
162 | function sendPost(server, jsonObj){
163 | return new Promise((resolve, reject) => {
164 | request.post({url: server, formData: jsonObj}, (err, httpResponse, body) => {
165 | if (err) {
166 | reject(err);
167 | }
168 | resolve(body);
169 | });
170 | });
171 | }
172 |
173 |
174 | module.exports.genKey = genKey;
175 | module.exports.getKeys = getKeys;
176 | module.exports.ipfsAddBuffer = ipfsAddBuffer;
177 | module.exports.ipfsAddObject = ipfsAddObject;
178 | module.exports.ipfsNameObject = ipfsNameObject;
179 | module.exports.ipfsPublish = ipfsPublish;
180 | module.exports.resolveName = resolveName;
181 | module.exports.getJsonObject = getJsonObject;
182 | module.exports.sendPost = sendPost;
183 |
184 |
--------------------------------------------------------------------------------
/common/slog.js:
--------------------------------------------------------------------------------
1 | var sio = require('./sio.js');
2 |
3 | function calculateSLogPayloads(sLog){
4 | let i, j;
5 |
6 | for (i = sLog.length - 1; i >= 0; i--){
7 | if (sLog[i].data.operation === 'remove' && sLog[i].data.target != null && !sLog[i].removed){
8 | for (j = i - 1; j >= 0; j--){
9 | if (sLog[j].address === sLog[i].data.target){
10 | sLog[j].removed = true;
11 | break;
12 | }
13 | }
14 | }
15 | }
16 |
17 | //TODO expand keyframes here
18 | }
19 |
20 | function subtractSLog(sLog, deletionSLog){
21 | let targets = [];
22 | if (deletionSLog) {
23 | for (let item of deletionSLog){
24 | if (!item.removed && item.payload && item.payload.data && item.payload.data.target){//TODO run through getSLogObj
25 | targets.push(item.payload.data.target);
26 | }
27 | }
28 | }
29 |
30 | for (let item of sLog){
31 | if (targets.indexOf(sLog.address) !== -1){
32 | item.removed = true;
33 | }
34 | }
35 | }
36 |
37 | function getLatestSeqno(sLog){
38 | for (let i = sLog.length - 1; i >= 0; i--){
39 | if (sLog[i].seqno != null){
40 | return sLog[i].seqno;
41 | }
42 | }
43 | return 0;
44 | }
45 |
46 | //gets data from object, visiting the address if necessary
47 | function getSLogObj(obj){
48 | return new Promise((resolve, reject) => {
49 | if (obj.data != null){
50 | resolve(obj);
51 | } else if (obj.address == null){
52 | reject('Malformed object: ' + obj);//obj can have a .address, but can also have a .data for speed. obj.address should point to the exact same JSON object as obj.data if it exists.
53 | } else {
54 | sio.getJsonObject(obj.address).then(result => {
55 | obj.data = result;
56 | resolve(obj);
57 | }).catch(reject);
58 | }
59 | });
60 | }
61 |
62 | function recursiveLoad(runningSLog, sLogLastGoodNext, insertAfter, depth, onCompletion, onFail){
63 | return function(sLogEntry){
64 | //sLogEntry.address = objAddress;
65 | if (runningSLog == null) {
66 | runningSLog = [];
67 | }
68 | if (sLogEntry.data.next != null && sLogEntry.data.next.address != null && sLogLastGoodNext === sLogEntry.data.next.address){
69 | //we know that if we see the same next value, we've already seen this node since this is an append-only linked list built on a merkel-dag
70 | if (onCompletion != null) {
71 | onCompletion(runningSLog);
72 | }
73 | } else {
74 | if (insertAfter === -1){
75 | runningSLog.unshift(sLogEntry);
76 | } else if (runningSLog.length === insertAfter + 1){
77 | runningSLog.push(sLogEntry);
78 | } else {
79 | runningSLog.splice(insertAfter + 1, 0, sLogEntry);
80 | }
81 |
82 | //recurse until we hit the list tail or we hit a recursion depth limit
83 | if (sLogEntry.data.next != null && (depth === -1 || depth > 0)){
84 | getSLogObj(sLogEntry.data.next).then(recursiveLoad(runningSLog, sLogLastGoodNext, insertAfter, depth === -1 ? depth : --depth, onCompletion, onFail)).catch(onFail);
85 | } else {
86 | if (onCompletion != null) {
87 | onCompletion(runningSLog);
88 | }
89 | }
90 | }
91 | };
92 | }
93 |
94 | function updateSLog(sLog, sLogAddress, depth){
95 | return new Promise((resolve, reject) => {
96 | var sLogLastGood;
97 | var insertAfter;
98 | if (sLog == null){
99 | sLogLastGood = 'default';
100 | insertAfter = -1;
101 | } else {
102 | if (sLog[sLog.length - 1].data.next) {
103 | sLogLastGood = sLog[sLog.length - 1].data.next.address;
104 | } else {
105 | sLogLastGood = 'default';
106 | }
107 | insertAfter = sLog.length - 1;
108 | }
109 |
110 | getSLogObj(sLogAddress).then(recursiveLoad(sLog, sLogLastGood, insertAfter, depth, resolve, reject)).catch(reject);
111 | });
112 | }
113 |
114 |
115 | function newSLogEntry(operation){
116 | var result = {
117 | timestamp: Date.now(),
118 | next: null,
119 | operation: operation,
120 | };
121 |
122 | return result;
123 | }
124 |
125 | module.exports.calculateSLogPayloads = calculateSLogPayloads;
126 | module.exports.subtractSLog = subtractSLog;
127 | module.exports.getSLogObj = getSLogObj;
128 | module.exports.updateSLog = updateSLog;
129 | module.exports.getLatestSeqno = getLatestSeqno;
130 | module.exports.newSLogEntry = newSLogEntry;
131 |
132 |
--------------------------------------------------------------------------------
/common/storage.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var wrappersFile = 'wrappers.json';
3 |
4 | function saveObject(filename, obj){//TODO deprecate this
5 | return new Promise((resolve, reject) => {
6 | fs.writeFile(filename, JSON.stringify(obj), (err) => {
7 | if (err) {
8 | reject(err);
9 | }
10 | resolve();
11 | });
12 | });
13 | }
14 |
15 | function saveWrappers(wrappers){
16 | //return new Promise((resolve, reject) => {
17 | return saveObject(wrappersFile, wrappers);//.then(resolve).catch(reject);
18 | //});
19 | }
20 |
21 | function loadObjectIfExists(filename){//TODO deprecate this, use something also browser-compatible
22 | return new Promise((resolve, reject) => {
23 | fs.readFile(filename, (err, data) => {
24 | if (err) {
25 | reject(err);
26 | }
27 | resolve(data);
28 | });
29 | });
30 | }
31 |
32 | function getWrappers(){
33 | return new Promise((resolve, reject) => {
34 | loadObjectIfExists(wrappersFile).then((data) => {
35 | resolve(JSON.parse(data));
36 | }).catch(() => {
37 | resolve({});
38 | });
39 | });
40 | }
41 | module.exports.saveObject = saveObject;
42 | module.exports.saveWrappers = saveWrappers;
43 | module.exports.loadObjectIfExists = loadObjectIfExists;
44 | module.exports.getWrappers = getWrappers;
45 |
--------------------------------------------------------------------------------
/common/storagebrowser.js:
--------------------------------------------------------------------------------
1 | function getCookie(cname) {
2 | var name = cname + '=';
3 | var ca = document.cookie.split(';');
4 | for(var i = 0; i < ca.length; i++) {
5 | var c = ca[i];
6 | while (c.charAt(0) === ' ') {
7 | c = c.substring(1);
8 | }
9 | if (c.indexOf(name) === 0) {
10 | return c.substring(name.length,c.length);
11 | }
12 | }
13 | return '';
14 | }
15 |
16 | function saveWrappers(wrappers){
17 | document.cookie = 'wrappers=' + JSON.stringify(wrappers) + '; path=/';
18 | }
19 |
20 | function getWrappers(){
21 | let currentData = getCookie('wrappers');
22 | if (currentData !== ''){
23 | return JSON.parse(currentData);
24 | }
25 | return {};
26 | }
27 |
28 | module.exports.saveWrappers = saveWrappers;
29 | module.exports.getWrappers = getWrappers;
30 |
--------------------------------------------------------------------------------
/common/util.js:
--------------------------------------------------------------------------------
1 | function propertyExists(obj, prop) {
2 | var parts = prop.split('.');
3 | for(var i = 0, l = parts.length; i < l; i++) {
4 | var part = parts[i];
5 | if(obj !== null && typeof obj === 'object' && part in obj) {
6 | obj = obj[part];
7 | } else {
8 | return false;
9 | }
10 | }
11 | return true;
12 | }
13 |
14 | function hashFromAddress(address){
15 | var hash = address.split('/');
16 | for (let i = 0; i < hash.length; i++){
17 | if (hash[i] === 'ipfs' || hash[i] === 'ipns'){
18 | return hash[i+1];
19 | }
20 | }
21 | return hash[hash.length - 1];
22 | }
23 |
24 | function rebuildKeys(rawKeys, daemonKeys){
25 | for (let keyId of Object.keys(rawKeys)){
26 | rawKeys[keyId].last = {address: rawKeys[keyId].last};
27 | }
28 | for (let key of daemonKeys.Keys){
29 | if (rawKeys['/ipns/' + key.Id] != null){
30 | rawKeys['/ipns/' + key.Id].name = key.Name;
31 | }
32 | }
33 | return rawKeys;
34 | }
35 |
36 | function prepareStorageKeys(rawKeys){
37 | var storageKeys = {};
38 | for (let keyId of Object.keys(rawKeys)){
39 | storageKeys[keyId] = {last: rawKeys[keyId].last.address, password: rawKeys[keyId].password};
40 | }
41 | return storageKeys;
42 | }
43 |
44 | function processQueue(obj, myFunc){
45 |
46 | if (!obj.funcQueue) {
47 | obj.funcQueue = [];
48 | }
49 |
50 | //myFunc must take (callback)
51 | if (myFunc) {
52 | obj.funcQueue.push({func: myFunc, running: false});
53 | }
54 |
55 | let somethingRunning = false;
56 | for (let item of obj.funcQueue) {
57 | if (item.running === true) {
58 | somethingRunning = true;
59 | }
60 | }
61 |
62 | if (!somethingRunning && obj.funcQueue.length > 0){
63 | obj.funcQueue[0].running = true;
64 | obj.funcQueue[0].func(() => {
65 | obj.funcQueue.shift();
66 | processQueue(obj);
67 | });
68 | }
69 | }
70 |
71 | function acquireLock(obj){
72 | return new Promise((resolve, reject) => {
73 | processQueue(obj, (releaseLock) => {
74 | resolve(releaseLock);
75 | });
76 | });
77 | }
78 |
79 | module.exports.propertyExists = propertyExists;
80 | module.exports.hashFromAddress = hashFromAddress;
81 | module.exports.rebuildKeys = rebuildKeys;
82 | module.exports.prepareStorageKeys = prepareStorageKeys;
83 | //module.exports.processQueue = processQueue;
84 | module.exports.acquireLock = acquireLock;
85 |
--------------------------------------------------------------------------------
/common/wrapperfuncs.js:
--------------------------------------------------------------------------------
1 | var sio = require('./sio.js');
2 | var slog = require('./slog.js');
3 | var util = require('./util.js');
4 | var payloads = require('./payloads.js');
5 |
6 | function getBlankWrapper(keyName){
7 | return {seqno: 0, name: keyName, last: {data: {info: {}, head: null}}};
8 | }
9 |
10 | function fillWrapper(wrapper, msg, serverAddress){
11 | wrapper.last.data.info.mode = msg.mode;
12 | wrapper.password = msg.password;
13 |
14 | if (msg.title && msg.mode !== 'thread') {
15 | wrapper.last.data.info.title = msg.title;
16 | }
17 | if (msg.formats) {
18 | wrapper.last.data.info.formats = Object.prototype.toString.call(msg.formats) === '[object Array]' ? msg.formats : [msg.formats];
19 | }
20 | if (msg.mods) {
21 | wrapper.last.data.info.mods = Object.prototype.toString.call(msg.mods) === '[object Array]' ? msg.mods : [msg.mods];
22 | }
23 | if (msg.threadservers) {
24 | wrapper.last.data.info.threadservers = Object.prototype.toString.call(msg.threadservers) === '[object Array]' ? msg.threadservers : [msg.threadservers];
25 | }
26 | if (msg.server){
27 | wrapper.last.data.info.server = msg.server;
28 | } else if (serverAddress){
29 | wrapper.last.data.info.serverconn = {ip: serverAddress};
30 | }
31 |
32 | }
33 |
34 | function loadWrapper(wrapper){
35 | return new Promise((resolve, reject) => {
36 | slog.getSLogObj(wrapper.last).then((result) => {
37 | wrapper.last = result;
38 | if (wrapper.last.data.head == null){
39 | return null;
40 | } else {//if (wrapper.sLog == null){
41 | return slog.updateSLog(wrapper.sLog, wrapper.last.data.head, -1);
42 | }// else {
43 | // return wrapper.sLog;
44 | //}
45 | }).then(sLog => {
46 | wrapper.sLog = sLog;
47 | if (wrapper.seqno == null && wrapper.sLog != null) {
48 | wrapper.seqno = slog.getLatestSeqno(wrapper.sLog);
49 | }
50 | resolve(wrapper);
51 | }).catch(reject);
52 | });
53 | }
54 |
55 | //TODO think about merging this into loadWrapper
56 | function pullWrapperStub(wrapperId, wrappers){
57 | return sio.resolveName(wrapperId).then(address => {
58 | if (wrappers[wrapperId] == null) {
59 | wrappers[wrapperId] = {last: {}};
60 | }
61 | wrappers[wrapperId].last.data = null;
62 | wrappers[wrapperId].last.address = address;
63 | return slog.getSLogObj(wrappers[wrapperId].last);
64 | });
65 | }
66 |
67 | function getServerConn(serverId, wrappers){
68 | //return new Promise((resolve, reject) => {
69 | let promise = null;
70 | if (wrappers[serverId] == null){
71 | promise = pullWrapperStub(serverId, wrappers);
72 | }
73 | return Promise.resolve(promise).then(() => {
74 | if (!util.propertyExists(wrappers[serverId], 'last.data.info.serverconn') || !wrappers[serverId].last.data.info.mode === 'server') {
75 | if (util.propertyExists(wrappers[serverId], 'last.data.info.server')){
76 | return getServerConn(wrappers[serverId].last.data.info.server, wrappers);
77 | } else {
78 | return Promise.reject('Not a valid server object.');
79 | }
80 | } else {
81 | return wrappers[serverId].last.data.info.serverconn;
82 | }
83 | });
84 | //});
85 | }
86 |
87 | function collectMods(mods, wrappers){
88 | let modsPending = [];
89 | if (mods != null){
90 | for (let mod of mods){
91 | modsPending.push(
92 | pullWrapperStub(mod, wrappers).then(() => {
93 | return loadWrapper(wrappers[mod]);
94 | }).then(() => {
95 | return slog.calculateSLogPayloads(wrappers[mod]);
96 | })
97 | );
98 | }
99 | }
100 | return modsPending;
101 | }
102 |
103 | function createObj(msg, wrappers, serverAddress){
104 | return new Promise((resolve, reject) => {
105 | let keyName = 'smug-' + Math.random().toString(36).substring(2);
106 | let newWrapper = getBlankWrapper(keyName);
107 | fillWrapper(newWrapper, msg, serverAddress);
108 | newWrapper.name = keyName;
109 |
110 | var key;
111 | sio.genKey(keyName).then(newKey => {
112 | key = '/ipns/' + newKey.Id;
113 | wrappers[key] = newWrapper;
114 | return sio.ipfsAddObject(newWrapper.last.data);
115 | }).then(newAddress => {
116 | wrappers[key].last.address = newAddress;
117 | return sio.ipfsPublish(wrappers[key].last.address, wrappers[key].name, key);
118 | }).then(() => {
119 | resolve({result: key});
120 | }).catch((err) => {
121 | reject({status: 500, result: err});
122 | });
123 | });
124 | }
125 |
126 | function addToObj(msg, wrappers, filesInfo, formats, previewable){
127 | return new Promise((resolve, reject) => {
128 | if (msg.id == null || msg.id === '' || wrappers[msg.id] == null){
129 | reject({status: 404, result: 'No object with that id found: ' + msg.id});
130 |
131 | //TODO verify that the selected wrapper actually uses the mode in question
132 | //wrappers[id].last.data.mode
133 | } else {
134 | let getPayload;
135 | switch(msg.mode){
136 | case 'thread':
137 | getPayload = payloads.getThreadPayload(msg, filesInfo, formats, previewable);
138 | break;
139 | case 'board':
140 | getPayload = payloads.getBoardPayload(msg);
141 | break;
142 | case 'site':
143 | getPayload = payloads.getSitePayload(msg);
144 | break;
145 | case 'mod':
146 | getPayload = payloads.getModPayload(msg);
147 | break;
148 | default:
149 | reject({status: 500, result: 'Unsupported operation.'});
150 | }
151 |
152 | let newEntry;
153 | let releaser;
154 | //let seqno;
155 | getPayload.then(payload => {
156 | newEntry = slog.newSLogEntry('add');
157 | newEntry.payload = payload;
158 | return util.acquireLock(wrappers[msg.id]);
159 | }).then(releaseLock => {
160 | releaser = releaseLock;
161 | return loadWrapper(wrappers[msg.id]);
162 | }).then(() => {
163 | newEntry.seqno = ++wrappers[msg.id].seqno;
164 | if (wrappers[msg.id].last.data.head) {
165 | newEntry.next = {address: wrappers[msg.id].last.data.head.address};
166 | }
167 | return sio.ipfsAddObject(newEntry);
168 | }).then(newEntryAddress => {
169 | if (wrappers[msg.id].last.data.info.mode === 'thread' && wrappers[msg.id].last.data.info.op == null){
170 | wrappers[msg.id].last.data.info.op = {address: newEntryAddress};
171 | }
172 | wrappers[msg.id].last.data.head = {address: newEntryAddress};
173 | return sio.ipfsAddObject(wrappers[msg.id].last.data);
174 | }).then(newAddress => {
175 | wrappers[msg.id].last.address = newAddress;
176 | //seqno = wrappers[msg.id].seqno;
177 | releaser();
178 | releaser = null;
179 | return sio.ipfsPublish(wrappers[msg.id].last.address, wrappers[msg.id].name, msg.id);
180 | }).then(() => {
181 | resolve({result: '' + wrappers[msg.id].seqno});
182 | }).catch(err => {
183 | if (releaser) {
184 | releaser();
185 | }
186 | reject({status: 500, result: err});
187 | });
188 |
189 | }
190 | });
191 | }
192 |
193 | function removeFromObj(msg, wrappers){
194 | return new Promise((resolve, reject) => {
195 | if (msg.id == null || msg.id === '' || wrappers[msg.id] == null){
196 | reject({status: 404, result: 'No object with that id found: ' + msg.id});
197 |
198 | //TODO verify that the selected wrapper actually uses the mode in question
199 | //wrappers[id].last.data.mode
200 | //or perhaps don't need to bother here?
201 | } else {
202 | let newEntry;
203 | let releaser;
204 |
205 | util.acquireLock(wrappers[msg.id]).then(releaseLock => {
206 | releaser = releaseLock;
207 | return loadWrapper(wrappers[msg.id]);
208 | }).then(() => {
209 | newEntry = slog.newSLogEntry('remove');
210 | newEntry.target = msg.target;
211 | if (wrappers[msg.id].last.data.head) {
212 | newEntry.next = {address: wrappers[msg.id].last.data.head.address};
213 | }
214 | return sio.ipfsAddObject(newEntry);
215 | }).then(newEntryAddress => {
216 | wrappers[msg.id].last.data.head = {address: newEntryAddress};
217 | return sio.ipfsAddObject(wrappers[msg.id].last.data);
218 | }).then(newAddress => {
219 | wrappers[msg.id].last.address = newAddress;
220 | releaser();
221 | releaser = null;
222 | return sio.ipfsPublish(wrappers[msg.id].last.address, wrappers[msg.id].name, msg.id);
223 | }).then(() => {
224 | resolve({result: 'Removal successful.'});
225 | }).catch(err => {
226 | if (releaser) {
227 | releaser();
228 | }
229 | reject({status: 500, result: err});
230 | });
231 | }
232 | });
233 | }
234 |
235 | function setObj(msg, wrappers, serverAddress){//TODO
236 | return new Promise((resolve, reject) => {
237 | if (msg.id == null || msg.id === '' || wrappers[msg.id] == null){
238 | reject({status: 404, result: 'No object with that id found: ' + msg.id});
239 |
240 | //TODO verify that the selected wrapper actually uses the mode in question
241 | //wrappers[id].last.data.mode
242 | } else {
243 | //if msg.lastdata
244 | //JSON.parse(msg.lastdata)
245 | //wrappers[msg.id].last = {data: msg.lastdata}
246 | //recalculate wrappers[msg.id].last.address
247 | //publish wrappers[msg.id].last.address to wrappers[msg.id].name (msg.id)
248 | //wrappers[msg.id].seqno = null
249 | //wrappers[msg.id].sLog = null
250 | }
251 | });
252 | }
253 |
254 | function deleteObj(msg, wrappers){
255 | return new Promise((resolve, reject) => {
256 | if (msg.id == null || msg.id === '' || wrappers[msg.id] == null){
257 | reject({status: 404, result: 'No object with that id found: ' + msg.id});
258 | } else {
259 | wrappers[msg.id] = null; //well that was easy
260 | resolve({result: msg.id + ' deleted.'});
261 | //TODO consider purging all object content from ipfs... but need to think about what if some content appears in multiple wrappers
262 | }
263 | });
264 | }
265 |
266 | module.exports.loadWrapper = loadWrapper;
267 | module.exports.pullWrapperStub = pullWrapperStub;
268 | module.exports.getServerConn = getServerConn;
269 | module.exports.collectMods = collectMods;
270 | module.exports.createObj = createObj;
271 | module.exports.addToObj = addToObj;
272 | module.exports.removeFromObj = removeFromObj;
273 | module.exports.setObj = setObj;
274 | module.exports.deleteObj = deleteObj;
275 |
276 |
--------------------------------------------------------------------------------
/install_all.sh:
--------------------------------------------------------------------------------
1 | export IPFS_PATH=./.ipfs
2 | ipfs init
3 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin "[\"*\"]"
4 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials "[\"true\"]"
5 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods "[\"PUT\", \"POST\", \"GET\"]"
6 |
7 | cd server
8 | npm install
9 | cd ../common
10 | npm install
11 | cd ../nameserver
12 | npm install
13 |
--------------------------------------------------------------------------------
/kill_all.sh:
--------------------------------------------------------------------------------
1 | pkill -F .ipfs/ipfs.pid
2 | pkill -F server/server.pid
3 | pkill -F nameserver/server.pid
4 |
5 | ./clean_pids.sh
6 |
--------------------------------------------------------------------------------
/nameserver/namemanager.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var cors = require('cors');
3 | var multer = require('multer');
4 | //var pem = require('pem');
5 | //var https = require('https');
6 | //var fs = require('fs');
7 |
8 | var upload = multer();
9 | //var sio = require('../common/sio.js');
10 | var storage = require('../common/storage.js');
11 |
12 | var app = express();
13 |
14 | app.use(cors());
15 |
16 | var sitePassword = require('../common/settings.js').password;
17 | var port = 3005; //0 makes the program just pick something free
18 |
19 | var bindings;
20 |
21 | app.post('/', upload.fields([]), function (req, res) {//TODO get rid of the damn file uploads
22 | console.log('POST received: ' + JSON.stringify(req.body));
23 |
24 | if (req.body != null){
25 | if (req.body.address != null && req.body.association != null && req.body.password != null){
26 | if (req.body.password === sitePassword){
27 | bindings[req.body.address] = req.body.association;
28 | storage.saveObject('bindings.json', bindings).then(console.log).catch(console.error);
29 | res.send('Name successfully registered.');
30 | } else {
31 | res.status(400).send('Wrong password.');
32 | }
33 | } else {
34 | res.status(400).send('Malformed POST (wrong fields).');
35 | }
36 | } else {
37 | res.status(400).send('Malformed POST (missing body).');
38 | }
39 | });
40 |
41 | app.get('/', upload.fields([]), function (req, res) {
42 | console.log('GET received: ' + JSON.stringify(req.query));
43 | //console.log(req);
44 | //res.set('Access-Control-Allow-Origin', '*');
45 |
46 | if (req.query != null && req.query.address != null){
47 | if (bindings[req.query.address] != null){
48 | res.send(bindings[req.query.address]);
49 | } else {
50 | res.status(404).send('Unknown name.');
51 | }
52 | } else {
53 | res.status(404).send('Malformed request.');
54 | }
55 | });
56 |
57 | bindings = storage.loadObjectIfExists('bindings.json').then(result => {
58 | bindings = result;
59 | }).catch(() => {
60 | bindings = {};
61 | });
62 |
63 | var listener = app.listen(port, function(){
64 | console.log('Now listening on port ' + listener.address().port);
65 | });
66 |
67 |
--------------------------------------------------------------------------------
/nameserver/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "namemanager",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "namemanager.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "BSD-2-Clause",
11 | "dependencies": {
12 | "cors": "^2.8.0",
13 | "express": "~4.14.0",
14 | "multer": "^1.1.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/populate_boards.sh:
--------------------------------------------------------------------------------
1 | echo "Populating boards..."
2 |
3 | #create board mods
4 | amod=$(node scripts/createmod.js $1)
5 | bmod=$(node scripts/createmod.js $1)
6 | cmod=$(node scripts/createmod.js $1)
7 | devmod=$(node scripts/createmod.js $1)
8 | polmod=$(node scripts/createmod.js $1)
9 | techmod=$(node scripts/createmod.js $1)
10 | vmod=$(node scripts/createmod.js $1)
11 |
12 | #create boards
13 | aboard=$(node scripts/createboard.js $1 $amod $1 "Animu & Mango")
14 | bboard=$(node scripts/createboard.js $1 $bmod $1 "Random")
15 | cboard=$(node scripts/createboard.js $1 $cmod $1 "Anime/Cute")
16 | devboard=$(node scripts/createboard.js $1 $devmod $1 "Smugboard Development")
17 | polboard=$(node scripts/createboard.js $1 $polmod $1 "Politically Incorrect")
18 | techboard=$(node scripts/createboard.js $1 $techmod $1 "Technology")
19 | vboard=$(node scripts/createboard.js $1 $vmod $1 "Video Games")
20 |
21 | #add boards to site
22 | ares=$(node scripts/addboardtosite.js $2 $aboard a)
23 | bres=$(node scripts/addboardtosite.js $2 $bboard b)
24 | cres=$(node scripts/addboardtosite.js $2 $cboard c)
25 | devres=$(node scripts/addboardtosite.js $2 $devboard dev)
26 | polres=$(node scripts/addboardtosite.js $2 $polboard pol)
27 | techres=$(node scripts/addboardtosite.js $2 $techboard tech)
28 | vres=$(node scripts/addboardtosite.js $2 $vboard v)
29 |
30 | echo "Done"
31 |
--------------------------------------------------------------------------------
/scripts/addboardtosite.js:
--------------------------------------------------------------------------------
1 | var sio = require('../common/sio.js');
2 | var serverAddress = require('../common/settings.js').serverAddress;
3 | var password = require('../common/settings.js').password;
4 | var port = 3010;
5 |
6 | if (process.argv.length < 5){
7 | console.error('node addboardtosite.js ');
8 | process.exit();
9 | }
10 |
11 | var site = process.argv[2];
12 | var board = process.argv[3];
13 | var uri = process.argv[4];
14 |
15 | sio.sendPost(serverAddress + ':' + port, {
16 | mode: 'site',
17 | type: 'add',
18 | password: password,
19 | id: site,
20 | newaddress: board,
21 | uri: uri
22 | }).then(console.log).catch(console.error);
23 |
24 |
--------------------------------------------------------------------------------
/scripts/addthreadtoboard.js:
--------------------------------------------------------------------------------
1 | var sio = require('../common/sio.js');
2 | var serverAddress = require('../common/settings.js').serverAddress;
3 | var password = require('../common/settings.js').password;
4 | var port = 3010;
5 |
6 | if (process.argv.length < 4){
7 | console.error('node addthreadtoboard.js ');
8 | process.exit();
9 | }
10 |
11 | var board = process.argv[2];
12 | var thread = process.argv[3];
13 |
14 | sio.sendPost(serverAddress + ':' + port, {
15 | mode: 'board',
16 | type: 'add',
17 | password: password,
18 | id: board,
19 | newaddress: thread
20 | }).then(console.log).catch(console.error);
21 |
22 |
--------------------------------------------------------------------------------
/scripts/createboard.js:
--------------------------------------------------------------------------------
1 | var sio = require('../common/sio.js');
2 | var serverAddress = require('../common/settings.js').serverAddress;
3 | var password = require('../common/settings.js').password;
4 | var port = 3010;
5 |
6 | if (process.argv.length < 6){
7 | console.error('node createboard.js ');
8 | process.exit();
9 | }
10 |
11 | var server = process.argv[2];
12 | var mods = process.argv[3];
13 | var threadServer = process.argv[4];
14 | var title = process.argv[5];
15 |
16 | sio.sendPost(serverAddress + ':' + port, {
17 | mode: 'board',
18 | type: 'create',
19 | password: password,
20 | title: title,
21 | formats: ['png', 'jpg'],
22 | mods: [mods],
23 | threadservers: [threadServer],
24 | server: server
25 | }).then(console.log).catch(console.error);
26 |
27 |
--------------------------------------------------------------------------------
/scripts/createmod.js:
--------------------------------------------------------------------------------
1 | var sio = require('../common/sio.js');
2 | var serverAddress = require('../common/settings.js').serverAddress;
3 | var password = require('../common/settings.js').password;
4 | var port = 3010;
5 |
6 | if (process.argv.length < 3){
7 | console.error('node createmod.js ');
8 | process.exit();
9 | }
10 |
11 | var server = process.argv[2];
12 |
13 | sio.sendPost(serverAddress + ':' + port, {
14 | mode: 'mod',
15 | type: 'create',
16 | password: password,
17 | title: 'Pockets the Mod',
18 | server: server
19 | }).then(console.log).catch(console.error);
20 |
--------------------------------------------------------------------------------
/scripts/createserver.js:
--------------------------------------------------------------------------------
1 | var sio = require('../common/sio.js');
2 | var serverAddress = require('../common/settings.js').serverAddress;
3 | var password = require('../common/settings.js').password;
4 | var port = 3010;
5 |
6 | sio.sendPost(serverAddress + ':' + port, {
7 | mode: 'server',
8 | type: 'create',
9 | password: password,
10 | title: 'Server-chan'
11 | }).then(console.log).catch(console.error);
12 |
--------------------------------------------------------------------------------
/scripts/createsite.js:
--------------------------------------------------------------------------------
1 | var sio = require('../common/sio.js');
2 | var serverAddress = require('../common/settings.js').serverAddress;
3 | var password = require('../common/settings.js').password;
4 | var port = 3010;
5 |
6 | if (process.argv.length < 4){
7 | console.error('node createsite.js ');
8 | process.exit();
9 | }
10 |
11 | var server = process.argv[2];
12 | var mods = process.argv[3];
13 |
14 | sio.sendPost(serverAddress + ':' + port, {
15 | mode: 'site',
16 | type: 'create',
17 | password: password,
18 | title: 'Smugchan',
19 | formats: ['png', 'jpg'],
20 | mods: [mods],
21 | server: server
22 | }).then(console.log).catch(console.error);
23 |
--------------------------------------------------------------------------------
/scripts/createthread.js:
--------------------------------------------------------------------------------
1 | var sio = require('../common/sio.js');
2 | var serverAddress = require('../common/settings.js').serverAddress;
3 | var password = require('../common/settings.js').password;
4 | var port = 3010;
5 |
6 | if (process.argv.length < 3){
7 | console.error('node createthread.js ');
8 | process.exit();
9 | }
10 |
11 | var server = process.argv[2];
12 |
13 | sio.sendPost(serverAddress + ':' + port, {
14 | mode: 'thread',
15 | type: 'create',
16 | password: password,
17 | title: 'My Thread',
18 | server: server
19 | }).then(console.log).catch(console.error);
20 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "smugserver",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node server.js"
9 | },
10 | "author": "smugdev",
11 | "license": "MIT",
12 | "dependencies": {
13 | "express": "^4.15.4",
14 | "multer": "^1.3.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express();
3 | var multer = require('multer');
4 | var storage = multer.memoryStorage();
5 | var upload = multer({ storage: storage });
6 | var serverAddress = require('../common/settings.js').serverAddress;
7 | var port = 3010; //0 makes the program just pick something free
8 |
9 | var handler = require('../common/inputhandler.js');
10 |
11 | var globalWrappers = {};
12 |
13 | //http server entry
14 | app.post('/', upload.single('file'), function (req, res, next) { //TODO work out what next is for
15 | console.log('Post received: ' + JSON.stringify(req.body));
16 | //console.log(req);
17 | //console.log(req.file);
18 | res.set('Access-Control-Allow-Origin', '*');
19 |
20 | if (req.body) {
21 | handler.handleInput(globalWrappers, req.body, [req.file], req.connection.remoteAddress, serverAddress).then(obj => {
22 | console.log(obj);
23 | if (obj.status == null) {
24 | res.send(obj.result);
25 | } else {
26 | res.status(obj.status).send(obj.result);
27 | }
28 | }).catch(obj => {
29 | console.error(obj);
30 | if (obj.status == null) {
31 | res.send(obj.result);
32 | } else {
33 | res.status(obj.status).send(obj.result);
34 | }
35 | });
36 | } else {
37 | res.status(400).send('Malformed POST (missing body).');
38 | }
39 | });
40 |
41 | var listener = app.listen(port, function(){
42 | serverAddress += ':' + listener.address().port;
43 | handler.setupServer(globalWrappers).then(() => {
44 | console.log('Now listening on ' + listener.address().port);
45 | }).catch(console.error);
46 | });
47 |
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
1 | #set up servers (start_all.sh must be run first)
2 | export IPFS_PATH=./.ipfs
3 |
4 | #create server object
5 | server=$(node scripts/createserver.js)
6 | echo "Server address: $server"
7 |
8 | #create site mod
9 | sitemod=$(node scripts/createmod.js $server)
10 | echo "Site global moderator: $sitemod"
11 |
12 | #create site
13 | site=$(node scripts/createsite.js $server $sitemod)
14 | echo "Site address: $site"
15 |
16 | #get public client
17 | client=$(browserify client/smugchan.js > client/bundle.js && ipfs add -rq client | tail -n1)
18 | echo "Client address: /ipfs/$client"
19 | echo "Complete link: http://localhost:8080/ipfs/$client/#$site"
20 |
21 | #create and add boards
22 | ./populate_boards.sh $server $site
23 |
24 | #create board mod
25 | #boardmod=$(node scripts/createmod.js $server)
26 |
27 | #create board
28 | #board=$(node scripts/createboard.js $server $boardmod $server)
29 |
30 |
31 |
32 |
33 |
34 | #echo "Board address: $board"
35 | #echo "Board moderator: $boardmod"
36 |
37 |
--------------------------------------------------------------------------------
/start_all.sh:
--------------------------------------------------------------------------------
1 | IPFS_PATH=./.ipfs ipfs daemon &
2 | echo $! > .ipfs/ipfs.pid
3 | cd nameserver
4 | node namemanager.js >> log.txt &
5 | echo $! > server.pid
6 | sleep 10s #TODO find a better way of working out when ipfs is all set up
7 | #echo "now starting other stuff"
8 | cd ../server
9 | node server.js >> log.txt &
10 | echo $! > server.pid
11 |
--------------------------------------------------------------------------------