Check-in [0be4562948]
Not logged in
Overview
Comment:steganography related software
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 0be45629487f0047c96bb372e521e1f3267153c5
User & Date: martin_vahi on 2018-03-21 17:38:58
Other Links: manifest | tags
Context
2018-03-21 18:18
banking & unclassified_references check-in: 3d7c129ddd user: martin_vahi tags: trunk
2018-03-21 17:38
steganography related software check-in: 0be4562948 user: martin_vahi tags: trunk
2018-03-21 16:27
omnetpp.org Network Simulator check-in: d8576ecc35 user: martin_vahi tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/COMMENTS.txt version [c14e34d3b2].



















>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9

The karasiq-nanoboard is a Scala based
steganography software that uses PNG images
as containers.

Home page:

    https://github.com/Karasiq/nanoboard

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/pull_new_version_from_git_repository.bash version [1f47fec18c].



































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#!/usr/bin/env bash
#==========================================================================
# Initial author: Martin.Vahi@softf1.com
# This file is in public domain.
#==========================================================================
S_FP_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
#--------------------------------------------------------------------------
# For copy-pasting to the ~/.bashrc
#
#     alias mmmv_cre_git_clone="cp $PATH_TO_THE<$S_FP_DIR>/pull_new_version_from_git_repository ./; mkdir -p ./the_repository_clones;"
#
#--------------------------------------------------------------------------


fun_assert_exists_on_path_t1 () {
    local S_NAME_OF_THE_EXECUTABLE=$1 # first function argument
    local S_TMP_0="\`which $S_NAME_OF_THE_EXECUTABLE 2>/dev/null\`"
    local S_TMP_1=""
    local S_TMP_2="S_TMP_1=$S_TMP_0"
    eval ${S_TMP_2}
    if [ "$S_TMP_1" == "" ] ; then
        echo ""
        echo "This bash script requires the \"$S_NAME_OF_THE_EXECUTABLE\" to be on the PATH."
        echo "GUID=='5b33d722-4ad8-491b-a330-43136020a1e7'"
        echo ""
        exit 1 # exit with error
    fi
} # fun_assert_exists_on_path_t1

fun_assert_exists_on_path_t1 "ruby"
fun_assert_exists_on_path_t1 "grep"
fun_assert_exists_on_path_t1 "date"
fun_assert_exists_on_path_t1 "git"


#--------------------------------------------------------------------------
S_TMP_0="`uname -a | grep -E [Ll]inux`"
if [ "$S_TMP_0" == "" ]; then
    S_TMP_0="`uname -a | grep -E [Bb][Ss][Dd]`"
    if [ "$S_TMP_0" == "" ]; then
        echo ""
        echo "  The classical command line utilities at "
        echo "  different operating systems, for example, Linux and BSD,"
        echo "  differ. This script is designed to run only on Linux and BSD."
        echo "  If You are willing to risk that some of Your data "
        echo "  is deleted and/or Your operating system instance"
        echo "  becomes permanently flawed, to the point that "
        echo "  it will not even boot, then You may edit the Bash script that "
        echo "  displays this error message by modifying the test that "
        echo "  checks for the operating system type."
        echo ""
        echo "  If You do decide to edit this Bash script, then "
        echo "  a recommendation is to test Your modifications "
        echo "  within a virtual machine or, if virtual machines are not"
        echo "  an option, as some new operating system user that does not have "
        echo "  any access to the vital data/files."
        echo "  GUID=='1ee02b82-4b99-47f3-8430-43136020a1e7'"
        echo ""
        echo "  Aborting script without doing anything."
        echo ""
        echo "GUID=='7f1be944-f283-4c07-b530-43136020a1e7'"
        echo ""
        exit 1 # exit with error
    fi
fi


#--------------------------------------------------------------------------

S_TIMESTAMP="`date +%Y`_`date +%m`_`date +%d`_T_`date +%H`h_`date +%M`min_`date +%S`s"
S_FP_ARCHIVE="$S_FP_DIR/archives/$S_TIMESTAMP"
S_FP_THE_REPOSITORY_CLONES="$S_FP_DIR/the_repository_clones"
mkdir -p $S_FP_THE_REPOSITORY_CLONES

#--------------------------------------------------------------------------
S_ARGV_0="$1"
SB_SKIP_ARCHIVING="f"

fun_init_sb_archive_and_archives_folder(){
    #--------
    if [ "$S_ARGV_0" == "skip_archiving" ]; then 
        SB_SKIP_ARCHIVING="t"
    fi
    if [ "$S_ARGV_0" == "ska" ]; then # abbreviation of "skip archiving"
        SB_SKIP_ARCHIVING="t"
    fi
    #--------
    if [ "$SB_SKIP_ARCHIVING" != "t" ]; then 
        mkdir -p $S_FP_ARCHIVE
    fi
    #--------
} # fun_init_sb_archive_and_archives_folder

fun_init_sb_archive_and_archives_folder

#--------------------------------------------------------------------------

AR_REPO_FOLDER_NAMES=()

fun_assemble_array_of_repository_clone_folder_names () {
    cd $S_FP_THE_REPOSITORY_CLONES
    local S_TMP_0="`ruby -e \"ar=Array.new; Dir.glob('*').each{|x| if File.directory? x then ar<<x end}; puts(ar.to_s.gsub('[','(').gsub(']',')').gsub(',',' '))\"`"
    cd $S_FP_DIR
    local S_TMP_1="AR_REPO_FOLDER_NAMES=$S_TMP_0"
    eval ${S_TMP_1}
} # fun_assemble_array_of_repository_clone_folder_names 

fun_assemble_array_of_repository_clone_folder_names 


fun_update () {
    #--------
    local S_FP_FUNC_UPDATE_ORIG="`pwd`"
    #--------
    for s_iter in ${AR_REPO_FOLDER_NAMES[@]}; do
         S_FOLDER_NAME_OF_THE_LOCAL_COPY="$s_iter"
         echo ""
         #----
         if [ "$SB_SKIP_ARCHIVING" != "t" ]; then 
             echo "            Archiving a copy of $S_FOLDER_NAME_OF_THE_LOCAL_COPY"
             cp -f -R $S_FP_THE_REPOSITORY_CLONES/$S_FOLDER_NAME_OF_THE_LOCAL_COPY $S_FP_ARCHIVE/
         else
             echo "            Skipping the archiving a copy of $S_FOLDER_NAME_OF_THE_LOCAL_COPY"
         fi
         #----
         cd $S_FP_THE_REPOSITORY_CLONES/$S_FOLDER_NAME_OF_THE_LOCAL_COPY
         echo "Checking out a newer version of $S_FOLDER_NAME_OF_THE_LOCAL_COPY"
         #--------
         # Downloads the newest version of the software to that folder.
         git checkout --force # overwrites local changes, like the "svn co"
         git pull --all --recurse-submodules --force # gets the submodules
         #----
         # http://stackoverflow.com/questions/1030169/easy-way-pull-latest-of-all-submodules
         git submodule update --init --recursive --force
         #--------
         cd $S_FP_DIR
    done
    cd $S_FP_FUNC_UPDATE_ORIG
} # fun_update 

fun_update # is a call to the function
echo ""

#==========================================================================

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/FETCH_HEAD version [7d32254f07].













>
>
>
>
>
>
1
2
3
4
5
6
020ae3c469c33e2d33ddab6411cc1b83d3d58166		branch 'master' of https://github.com/Karasiq/nanoboard
2bd19ceac4d7c10db8ab121e27eea7eb32f28e45	not-for-merge	branch 'captcha' of https://github.com/Karasiq/nanoboard
0732b898d1d18c1a892a97377c0775adb6d01b12	not-for-merge	branch 'new-format' of https://github.com/Karasiq/nanoboard
020ae3c469c33e2d33ddab6411cc1b83d3d58166	not-for-merge	tag 'v1.3.2' of https://github.com/Karasiq/nanoboard
b423e5a6ed750f2f5131edb7eb0bbe66617b8c30	not-for-merge	tag 'v1.3.0' of https://github.com/Karasiq/nanoboard
efaedd598892e3118401c5e7c6258bb563651962	not-for-merge	tag 'v1.3.1' of https://github.com/Karasiq/nanoboard

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/HEAD version [acbaef275e].



>
1
ref: refs/heads/master

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/ORIG_HEAD version [02cc7799b7].



>
1
eaa9bd05c684736620ba0aa89de29816ee118628

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/config version [d6a676db81].























>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[remote "origin"]
	url = https://github.com/Karasiq/nanoboard.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
	remote = origin
	merge = refs/heads/master

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/description version [9635f1b7e1].



>
1
Unnamed repository; edit this file 'description' to name the repository.

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/hooks/applypatch-msg.sample version [86b9655a9e].































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.  The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".

. git-sh-setup
test -x "$GIT_DIR/hooks/commit-msg" &&
	exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
:

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/hooks/commit-msg.sample version [ee1ed5aad9].

















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message.  The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit.  The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".

# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

# This example catches duplicate Signed-off-by lines.

test "" = "$(grep '^Signed-off-by: ' "$1" |
	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
	echo >&2 Duplicate Signed-off-by lines.
	exit 1
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/hooks/post-update.sample version [b614c2f63d].

















>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".

exec git update-server-info

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/hooks/pre-applypatch.sample version [42fa415649].





























>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".

. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
	exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
:

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/hooks/pre-commit.sample version [36aed8976d].



































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments.  The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".

if git rev-parse --verify HEAD >/dev/null 2>&1
then
	against=HEAD
else
	# Initial commit: diff against an empty tree object
	against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --bool hooks.allownonascii)

# Redirect output to stderr.
exec 1>&2

# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
	# Note that the use of brackets around a tr range is ok here, (it's
	# even required, for portability to Solaris 10's /usr/bin/tr), since
	# the square bracket bytes happen to fall in the designated range.
	test $(git diff --cached --name-only --diff-filter=A -z $against |
	  LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
	cat <<\EOF
Error: Attempt to add a non-ASCII file name.

This can cause problems if you want to work with people on other platforms.

To be portable it is advisable to rename the file.

If you know what you are doing you can disable this check using:

  git config hooks.allownonascii true
EOF
	exit 1
fi

# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/hooks/pre-push.sample version [b4ad74c989].













































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/bin/sh

# An example hook script to verify what is about to be pushed.  Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed.  If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
#   <local ref> <local sha1> <remote ref> <remote sha1>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).

remote="$1"
url="$2"

z40=0000000000000000000000000000000000000000

IFS=' '
while read local_ref local_sha remote_ref remote_sha
do
	if [ "$local_sha" = $z40 ]
	then
		# Handle delete
		:
	else
		if [ "$remote_sha" = $z40 ]
		then
			# New branch, examine all commits
			range="$local_sha"
		else
			# Update to existing branch, examine new commits
			range="$remote_sha..$local_sha"
		fi

		# Check for WIP commit
		commit=`git rev-list -n 1 --grep '^WIP' "$range"`
		if [ -n "$commit" ]
		then
			echo "Found WIP commit in $local_ref, not pushing"
			exit 1
		fi
	fi
done

exit 0

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/hooks/pre-rebase.sample version [5885a56ab4].



















































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.

publish=next
basebranch="$1"
if test "$#" = 2
then
	topic="refs/heads/$2"
else
	topic=`git symbolic-ref HEAD` ||
	exit 0 ;# we do not interrupt rebasing detached HEAD
fi

case "$topic" in
refs/heads/??/*)
	;;
*)
	exit 0 ;# we do not interrupt others.
	;;
esac

# Now we are dealing with a topic branch being rebased
# on top of master.  Is it OK to rebase it?

# Does the topic really exist?
git show-ref -q "$topic" || {
	echo >&2 "No such branch $topic"
	exit 1
}

# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
	echo >&2 "$topic is fully merged to master; better remove it."
	exit 1 ;# we could allow it, but there is no point.
fi

# Is topic ever merged to next?  If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master           ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
	not_in_topic=`git rev-list "^$topic" master`
	if test -z "$not_in_topic"
	then
		echo >&2 "$topic is already up-to-date with master"
		exit 1 ;# we could allow it, but there is no point.
	else
		exit 0
	fi
else
	not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
	/usr/bin/perl -e '
		my $topic = $ARGV[0];
		my $msg = "* $topic has commits already merged to public branch:\n";
		my (%not_in_next) = map {
			/^([0-9a-f]+) /;
			($1 => 1);
		} split(/\n/, $ARGV[1]);
		for my $elem (map {
				/^([0-9a-f]+) (.*)$/;
				[$1 => $2];
			} split(/\n/, $ARGV[2])) {
			if (!exists $not_in_next{$elem->[0]}) {
				if ($msg) {
					print STDERR $msg;
					undef $msg;
				}
				print STDERR " $elem->[1]\n";
			}
		}
	' "$topic" "$not_in_next" "$not_in_master"
	exit 1
fi

exit 0

################################################################

This sample hook safeguards topic branches that have been
published from being rewound.

The workflow assumed here is:

 * Once a topic branch forks from "master", "master" is never
   merged into it again (either directly or indirectly).

 * Once a topic branch is fully cooked and merged into "master",
   it is deleted.  If you need to build on top of it to correct
   earlier mistakes, a new topic branch is created by forking at
   the tip of the "master".  This is not strictly necessary, but
   it makes it easier to keep your history simple.

 * Whenever you need to test or publish your changes to topic
   branches, merge them into "next" branch.

The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.

With this workflow, you would want to know:

(1) ... if a topic branch has ever been merged to "next".  Young
    topic branches can have stupid mistakes you would rather
    clean up before publishing, and things that have not been
    merged into other branches can be easily rebased without
    affecting other people.  But once it is published, you would
    not want to rewind it.

(2) ... if a topic branch has been fully merged to "master".
    Then you can delete it.  More importantly, you should not
    build on top of it -- other people may already want to
    change things related to the topic as patches against your
    "master", so if you need further changes, it is better to
    fork the topic (perhaps with the same name) afresh from the
    tip of "master".

Let's look at this example:

		   o---o---o---o---o---o---o---o---o---o "next"
		  /       /           /           /
		 /   a---a---b A     /           /
		/   /               /           /
	       /   /   c---c---c---c B         /
	      /   /   /             \         /
	     /   /   /   b---b C     \       /
	    /   /   /   /             \     /
    ---o---o---o---o---o---o---o---o---o---o---o "master"


A, B and C are topic branches.

 * A has one fix since it was merged up to "next".

 * B has finished.  It has been fully merged up to "master" and "next",
   and is ready to be deleted.

 * C has not merged to "next" at all.

We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.

To compute (1):

	git rev-list ^master ^topic next
	git rev-list ^master        next

	if these match, topic has not merged in next at all.

To compute (2):

	git rev-list master..topic

	if this is empty, it is fully merged to "master".

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/hooks/prepare-commit-msg.sample version [2b6275eda3].









































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source.  The hook's purpose is to edit the commit
# message file.  If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".

# This hook includes three examples.  The first comments out the
# "Conflicts:" part of a merge commit.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output.  It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited.  This is rarely a good idea.

case "$2,$3" in
  merge,)
    /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;

# ,|template,)
#   /usr/bin/perl -i.bak -pe '
#      print "\n" . `git diff --cached --name-status -r`
#	 if /^#/ && $first++ == 0' "$1" ;;

  *) ;;
esac

# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/hooks/update.sample version [39355a0759].

































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/bin/sh
#
# An example hook script to blocks unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
#   This boolean sets whether unannotated tags will be allowed into the
#   repository.  By default they won't be.
# hooks.allowdeletetag
#   This boolean sets whether deleting tags will be allowed in the
#   repository.  By default they won't be.
# hooks.allowmodifytag
#   This boolean sets whether a tag may be modified after creation. By default
#   it won't be.
# hooks.allowdeletebranch
#   This boolean sets whether deleting branches will be allowed in the
#   repository.  By default they won't be.
# hooks.denycreatebranch
#   This boolean sets whether remotely creating branches will be denied
#   in the repository.  By default this is allowed.
#

# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"

# --- Safety check
if [ -z "$GIT_DIR" ]; then
	echo "Don't run this script from the command line." >&2
	echo " (if you want, you could supply GIT_DIR then run" >&2
	echo "  $0 <ref> <oldrev> <newrev>)" >&2
	exit 1
fi

if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
	echo "usage: $0 <ref> <oldrev> <newrev>" >&2
	exit 1
fi

# --- Config
allowunannotated=$(git config --bool hooks.allowunannotated)
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
denycreatebranch=$(git config --bool hooks.denycreatebranch)
allowdeletetag=$(git config --bool hooks.allowdeletetag)
allowmodifytag=$(git config --bool hooks.allowmodifytag)

# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
	echo "*** Project description file hasn't been set" >&2
	exit 1
	;;
esac

# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero="0000000000000000000000000000000000000000"
if [ "$newrev" = "$zero" ]; then
	newrev_type=delete
else
	newrev_type=$(git cat-file -t $newrev)
fi

case "$refname","$newrev_type" in
	refs/tags/*,commit)
		# un-annotated tag
		short_refname=${refname##refs/tags/}
		if [ "$allowunannotated" != "true" ]; then
			echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
			echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
			exit 1
		fi
		;;
	refs/tags/*,delete)
		# delete tag
		if [ "$allowdeletetag" != "true" ]; then
			echo "*** Deleting a tag is not allowed in this repository" >&2
			exit 1
		fi
		;;
	refs/tags/*,tag)
		# annotated tag
		if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
		then
			echo "*** Tag '$refname' already exists." >&2
			echo "*** Modifying a tag is not allowed in this repository." >&2
			exit 1
		fi
		;;
	refs/heads/*,commit)
		# branch
		if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
			echo "*** Creating a branch is not allowed in this repository" >&2
			exit 1
		fi
		;;
	refs/heads/*,delete)
		# delete branch
		if [ "$allowdeletebranch" != "true" ]; then
			echo "*** Deleting a branch is not allowed in this repository" >&2
			exit 1
		fi
		;;
	refs/remotes/*,commit)
		# tracking branch
		;;
	refs/remotes/*,delete)
		# delete tracking branch
		if [ "$allowdeletebranch" != "true" ]; then
			echo "*** Deleting a tracking branch is not allowed in this repository" >&2
			exit 1
		fi
		;;
	*)
		# Anything else (is there anything else?)
		echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
		exit 1
		;;
esac

# --- Finished
exit 0

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/index version [e8756a1baf].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/info/exclude version [c879df015d].













>
>
>
>
>
>
1
2
3
4
5
6
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/logs/HEAD version [b88dae22e6].





>
>
1
2
0000000000000000000000000000000000000000 eaa9bd05c684736620ba0aa89de29816ee118628 Martin Vahi <martin.vahi@softf1.com> 1512461959 +0200	clone: from https://github.com/Karasiq/nanoboard.git
eaa9bd05c684736620ba0aa89de29816ee118628 020ae3c469c33e2d33ddab6411cc1b83d3d58166 Martin Vahi <martin.vahi@softf1.com> 1521653750 +0200	pull --all --recurse-submodules --force: Fast-forward

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/logs/refs/heads/master version [b88dae22e6].





>
>
1
2
0000000000000000000000000000000000000000 eaa9bd05c684736620ba0aa89de29816ee118628 Martin Vahi <martin.vahi@softf1.com> 1512461959 +0200	clone: from https://github.com/Karasiq/nanoboard.git
eaa9bd05c684736620ba0aa89de29816ee118628 020ae3c469c33e2d33ddab6411cc1b83d3d58166 Martin Vahi <martin.vahi@softf1.com> 1521653750 +0200	pull --all --recurse-submodules --force: Fast-forward

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/logs/refs/remotes/origin/HEAD version [149bc70ffc].



>
1
0000000000000000000000000000000000000000 eaa9bd05c684736620ba0aa89de29816ee118628 Martin Vahi <martin.vahi@softf1.com> 1512461959 +0200	clone: from https://github.com/Karasiq/nanoboard.git

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/logs/refs/remotes/origin/master version [9bbab0b7da].



>
1
eaa9bd05c684736620ba0aa89de29816ee118628 020ae3c469c33e2d33ddab6411cc1b83d3d58166 ts2 <ts2@linux-0fiz.(none)> 1521653750 +0200	pull --all --recurse-submodules --force: fast-forward

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/objects/pack/pack-37adcc4d0ff940d37a85783a4e51a6409f1a11ac.idx version [41ce56438d].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/objects/pack/pack-37adcc4d0ff940d37a85783a4e51a6409f1a11ac.pack version [f0a3e29251].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/objects/pack/pack-fe6b0ba5600084ffbdda6101cc2162648e45780d.idx version [63ab72ef98].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/objects/pack/pack-fe6b0ba5600084ffbdda6101cc2162648e45780d.pack version [57433aca69].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/packed-refs version [daf0e0878b].















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# pack-refs with: peeled fully-peeled 
2bd19ceac4d7c10db8ab121e27eea7eb32f28e45 refs/remotes/origin/captcha
eaa9bd05c684736620ba0aa89de29816ee118628 refs/remotes/origin/master
0732b898d1d18c1a892a97377c0775adb6d01b12 refs/remotes/origin/new-format
a72caa8b4e9a4ac02160f0ae6ceae0376034a12b refs/tags/v1.0.0
^9e6a3dc4d9f66ebbd7f0c2fbb33465c2f2a51321
75d4548b3f9504b076a9fb562936090b00709569 refs/tags/v1.0.1
^0a7bde79e47ccbad208d8a0032f8508bf0929147
cc5cf1f0f80a85fee0b21419a44153d6712aad1a refs/tags/v1.0.2
3dc3b7070817c2c04108255fab024ea1cdb8c163 refs/tags/v1.0.3
^3aa25556e4ea515088459bbded3b527c33fd4826
b270d7cbd7b52c46a6569af5100e5d43f649401b refs/tags/v1.0.4
68e3436e4d9d49e674a2c3c8856f71338fa2988d refs/tags/v1.0.5
^eb335d9f02187d4cb6b4ea67aa2250f54a2eb270
d9298ac824b4edec2e9e774d7ab6ccfb08f27e7b refs/tags/v1.0.5-M1
4bbd201d8c345fd44e0d578d7e25da5677a35d03 refs/tags/v1.0.6
^3534d9e90d85858f09c8847794330d1dc78cbb9e
5c6df7234e7d71547aac26443b5fcc45a29a5543 refs/tags/v1.0.7
^f3149f8605c5f20bfaa29991f4603cdc77813d9c
4f9421c300d520b52461f3c880bfcd99020edec8 refs/tags/v1.1.0
^9628f0687a2c860adbdce42bc0e7f1f1ede0606b
0dd6978e8aaa4216351f20d5493d13561d16570c refs/tags/v1.2.0
^7016f4bf24e08bd3729e9ca65add5b150d4ece9d

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/refs/heads/master version [291d62e8ac].



>
1
020ae3c469c33e2d33ddab6411cc1b83d3d58166

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/refs/remotes/origin/HEAD version [d9427cda09].



>
1
ref: refs/remotes/origin/master

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/refs/remotes/origin/master version [291d62e8ac].



>
1
020ae3c469c33e2d33ddab6411cc1b83d3d58166

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/refs/tags/v1.3.0 version [5978efc2ec].



>
1
b423e5a6ed750f2f5131edb7eb0bbe66617b8c30

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/refs/tags/v1.3.1 version [3581810989].



>
1
efaedd598892e3118401c5e7c6258bb563651962

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.git/refs/tags/v1.3.2 version [291d62e8ac].



>
1
020ae3c469c33e2d33ddab6411cc1b83d3d58166

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.gitignore version [4daa174306].







>
>
>
1
2
3
.idea
target
*.bat

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/.travis.yml version [86e84b4e5a].

















>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
language: scala
scala:
  - 2.11.8
jdk:
  - oraclejdk8
script:
  - sbt ++$TRAVIS_SCALA_VERSION compile nanoboard/test nanoboard-server/test
sudo: false

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/LICENSE version [211af68d19].









































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.

"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.

2. Grant of Copyright License.

Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.

3. Grant of Patent License.

Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.

4. Redistribution.

You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:

    You must give any other recipients of the Work or Derivative Works a copy of this License; and
    You must cause any modified files to carry prominent notices stating that You changed the files; and
    You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
    If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.

You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.

5. Submission of Contributions.

Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.

6. Trademarks.

This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty.

Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.

8. Limitation of Liability.

In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability.

While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/README.MD version [4376a475b9].















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# karasiq-nanoboard [![Build Status](https://travis-ci.org/Karasiq/nanoboard.svg?branch=master)](https://travis-ci.org/Karasiq/nanoboard) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.karasiq/nanoboard_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.karasiq/nanoboard_2.12)
Scala [nanoboard](https://github.com/nanoboard/nanoboard) implementation.
![nanoboard](https://raw.github.com/Karasiq/nanoboard/master/images/screenshot.png)

# [Original project description](https://github.com/nanoboard/nanoboard/releases)
>What is Nanoboard: it is steganographic imageboard without centralized server or p2p: users share nanoposts by posting png-containers (with nanoposts hidden inside) on real imageboards (users negotiate which imageboard threads to use for posting containers in special Nanoboard thread).
>
>Nanoboard's goals are: speech of freedom, immortality and ownership of the imageboard. Nanoboard is able to use transport different from png-containers. One such alternative transport is: [BitMessage transport](https://github.com/nanoboard/nanoboard-bittransport).

See also: 
* https://wiki.1chan.ca/Наноборда
* https://github.com/RosinSmoke/nanoboard/blob/1a0d72cb155f8bee0e59cd16feb4c6a24d101436/README.md
* http://blog.andersen.im/2014/11/hiding-your-bits-in-the-bytes/

# Features
* Accepted containers management
* Automatic containers download
* Bootstrap typography BB-codes
* Code highlighting
* External images expansion
* File sharing
* Fractal music
* Highly optimized POW algorithm
* HTML5 video player
* Linked posts preview
* Live update (WebSockets)
* Localized interface
* Markdown formatting support
* Message live preview
* Option to verify existing posts
* Random container generation
* SVG images support
* Six themes

# How to use
* [Download latest release](https://github.com/Karasiq/nanoboard/releases)
  * Install and launch application using Windows installer 
  * Or extract zip package and enter `./bin/nanoboard` in console 
* Open <http://localhost:7347/> in your browser

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/build.sbt version [63560b8ce5].



































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import sbtassembly.Plugin.AssemblyKeys._

lazy val commonSettings = Seq(
  organization := "com.github.karasiq",
  version := "1.3.2",
  isSnapshot := version.value.endsWith("SNAPSHOT"),
  scalaVersion := "2.12.4"
)

lazy val librarySettings = Seq(
  name := "nanoboard",
  libraryDependencies ++= {
    val akkaV = "2.5.10"
    Seq(
      "com.typesafe.akka" %% "akka-actor" % akkaV,
      "org.scalatest" %% "scalatest" % "3.0.5" % "test",
      "com.typesafe.akka" %% "akka-stream" % akkaV,
      "com.typesafe.akka" %% "akka-http" % "10.0.11",
      "commons-codec" % "commons-codec" % "1.11",
      "commons-io" % "commons-io" % "2.6",
      "org.bouncycastle" % "bcprov-jdk15on" % "1.59",
      "net.i2p.crypto" % "eddsa" % "0.2.0",
      "org.jsoup" % "jsoup" % "1.11.2",
      "com.typesafe.play" %% "play-json" % "2.6.7",
      "com.lihaoyi" %% "scalatags" % "0.6.7",
      "com.upokecenter" % "cbor" % "3.0.3"
    )
  },
  publishMavenStyle := true,
  publishTo := {
    val nexus = "https://oss.sonatype.org/"
    if (isSnapshot.value)
      Some("snapshots" at nexus + "content/repositories/snapshots")
    else
      Some("releases" at nexus + "service/local/staging/deploy/maven2")
  },
  publishArtifact in Test := false,
  pomIncludeRepository := { _ ⇒ false },
  licenses := Seq("Apache License, Version 2.0" → url("http://opensource.org/licenses/Apache-2.0")),
  homepage := Some(url(s"https://github.com/Karasiq/${name.value}")),
  pomExtra := <scm>
    <url>git@github.com:Karasiq/{name.value}.git</url>
    <connection>scm:git:git@github.com:Karasiq/{name.value}.git</connection>
  </scm>
    <developers>
      <developer>
        <id>karasiq</id>
        <name>Piston Karasiq</name>
        <url>https://github.com/Karasiq</url>
      </developer>
    </developers>
)

lazy val backendSettings = Seq(
  name := "nanoboard-server",
  libraryDependencies ++= Seq(
    "com.typesafe.slick" %% "slick" % "3.2.1",
    "com.h2database" % "h2" % "1.4.196",
    "org.slf4j" % "slf4j-nop" % "1.7.25",
    "org.scalatest" %% "scalatest" % "3.0.5" % "test"
  ),
  mainClass in Compile := Some("com.karasiq.nanoboard.server.Main"),
  mainClass in assembly := (mainClass in Compile).value,
  jarName in assembly := "nanoboard-server.jar",
  test in assembly := {},
  mappings in Universal := {
    val universalMappings = (mappings in Universal).value
    val fatJar = (assembly in Compile).value
    val filtered = universalMappings.filterNot(_._2.endsWith(".jar"))
    filtered :+ (fatJar → ("lib/" + fatJar.getName))
  },
  scriptClasspath := Seq((jarName in assembly).value),
  scalaJsBundlerCompile in Compile <<= (scalaJsBundlerCompile in Compile).dependsOn(fullOptJS in Compile in frontend),
  scalaJsBundlerAssets in Compile += {
    import com.karasiq.scalajsbundler.dsl._

    val bootstrap = github("twbs", "bootstrap", "v3.3.6") / "dist"
    val fontAwesome = github("FortAwesome", "Font-Awesome", "v4.5.0")
    val videoJs = github("videojs", "video.js", "v5.8.0") / "dist"
    val highlightJs = "org.webjars" % "highlightjs" % "9.2.0"
    val jsDeps = Seq(
      // jQuery
      Script from url("https://code.jquery.com/jquery-2.1.4.min.js"),

      // Bootstrap
      Style from bootstrap / "css" / "bootstrap.css",
      Script from bootstrap / "js" / "bootstrap.js",

      // Font Awesome
      Style from fontAwesome / "css" / "font-awesome.css",

      // Video.js
      Script from videoJs / "video.min.js",
      Style from videoJs / "video-js.min.css",
      Static("video-js.swf") from videoJs / "video-js.swf",

      // Plugins
      Script from github("eXon", "videojs-youtube", "v2.0.8") / "dist" / "Youtube.min.js",

      // Noty.js
      Script from github("needim", "noty", "v2.3.8") / "js" / "noty" / "packaged" / "jquery.noty.packaged.min.js",

      // Moment.js
      Script from github("moment", "moment", "2.12.0") / "min" / "moment-with-locales.min.js",
      
      // Marked
      Script from "org.webjars.bower" % "marked" % "0.3.5" / "marked.min.js",

      // Tab Override
      Script from github("wjbryant", "taboverride", "4.0.3") / "build" / "output" / "taboverride.min.js",

      // Highlight.js
      Script from highlightJs / "highlight.min.js",
      Style from highlightJs / s"styles/${NanoboardAssets.highlightJsStyle}.css"
    )

    val highlightJsLanguages = for (lang ← NanoboardAssets.highlightJsLanguages)
      yield Script from highlightJs / s"languages/$lang.min.js"

    val staticDir = (baseDirectory in frontend)(_ / "files").value
    val staticFiles = Seq(
      Html from NanoboardAssets.index,
      Style from NanoboardAssets.style,
      Script from staticDir / "img2base64.js",
      Script from staticDir / "modernizr.js",
      Image("favicon.ico") from staticDir / "favicon.ico",
      Image("img/muon_bg.jpg") from staticDir / "muon_bg.jpg",
      Image("img/muon_posts.jpg") from staticDir / "muon_posts.jpg",
      Image("img/muon_inputs.jpg") from staticDir / "muon_inputs.jpg"
    )

    val fonts =
      (fontAwesome / "fonts" / "fontawesome-webfont").fonts() ++
      (videoJs / "font" / "VideoJS").fonts(dir = "font", extensions = Seq("eot", "svg", "ttf", "woff"))

    Bundle("index", jsDeps, highlightJsLanguages, staticFiles, fonts, scalaJsApplication(frontend, launcher = false).value)
  }
)

lazy val frontendSettings = Seq(
  scalaJSUseMainModuleInitializer := true,
  name := "nanoboard-frontend",
  resolvers += Resolver.sonatypeRepo("snapshots"),
  libraryDependencies ++= Seq(
    "com.chuusai" %%% "shapeless" % "2.3.3",
    "org.parboiled" %%% "parboiled" % "2.1.4",
    "com.github.karasiq" %%% "scalajs-bootstrap" % "2.3.1",
    "com.github.karasiq" %%% "scalajs-videojs" % "1.0.5",
    "com.github.karasiq" %%% "scalajs-marked" % "1.0.2",
    "ru.pavkin" %%% "scala-js-momentjs" % "0.9.1",
    "com.lihaoyi" %%% "scalatags" % "0.6.7"
  )
)

lazy val shared = crossProject.in(file("shared"))
  .settings(commonSettings:_*)
  .settings(
    name := "nanoboard-shared",
    libraryDependencies += "io.suzaku" %%% "boopickle" % "1.2.6"
  )

lazy val sharedJVM = shared.jvm

lazy val sharedJS = shared.js

lazy val library = Project("nanoboard", file("library"))
  .settings(commonSettings, librarySettings)

lazy val backend = Project("nanoboard-server", file("."))
  .dependsOn(library, sharedJVM)
  .settings(assemblySettings, commonSettings, backendSettings)
  .enablePlugins(ScalaJSBundlerPlugin, JavaAppPackaging)

lazy val frontend = Project("nanoboard-frontend", file("frontend"))
  .dependsOn(sharedJS)
  .settings(commonSettings, frontendSettings)
  .enablePlugins(ScalaJSPlugin)

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/files/favicon.ico version [bdfad40b11].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/files/img2base64.js version [21efdb6bee].























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
var img2base64 = {
    sharpen: function (ctx, w, h, mix) {
        var weights = [0, -1, 0, -1, 5, -1, 0, -1, 0],
            katet = Math.round(Math.sqrt(weights.length)),
            half = (katet * 0.5) | 0,
            dstData = ctx.createImageData(w, h),
            dstBuff = dstData.data,
            srcBuff = ctx.getImageData(0, 0, w, h).data,
            y = h;
        while (y--) {
            x = w;
            while (x--) {
                var sy = y, sx = x, dstOff = (y * w + x) * 4, r = 0, g = 0, b = 0, a = 0;
                for (var cy = 0; cy < katet; cy++) {
                    for (var cx = 0; cx < katet; cx++) {

                        var scy = sy + cy - half;
                        var scx = sx + cx - half;

                        if (scy >= 0 && scy < h && scx >= 0 && scx < w) {

                            var srcOff = (scy * w + scx) * 4;
                            var wt = weights[cy * katet + cx];

                            r += srcBuff[srcOff] * wt;
                            g += srcBuff[srcOff + 1] * wt;
                            b += srcBuff[srcOff + 2] * wt;
                            a += srcBuff[srcOff + 3] * wt;
                        }
                    }
                }

                dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix);
                dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix);
                dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix)
                dstBuff[dstOff + 3] = srcBuff[dstOff + 3];
            }
        }

        ctx.putImageData(dstData, 0, 0);
    },
    drawImage: function (file, compress, imgType, imageScale, quality, sharpness, success, error) {
        var reader = new FileReader();
        reader.onerror = error;
        reader.onloadend = function () {
            var res = reader.result;
            if (!compress) {
                success(res);
                return;
            }
            var canvas = document.createElement("canvas");
            var ctx = canvas.getContext("2d");
            img = new Image();
            img.onerror = error;
            img.onload = function () {
                canvas.width = img.width;
                canvas.height = img.height;
                ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
                img2base64.sharpen(ctx, img.width, img.height, sharpness / 100.0);
                var scale = 1.0 / (imageScale / 100.0);
                var shr = new Image();
                shr.onerror = error;
                shr.onload = function () {
                    canvas.width = img.width / scale;
                    canvas.height = img.height / scale;
                    ctx.drawImage(shr, 0, 0, img.width, img.height, 0, 0, img.width / scale, img.height / scale);
                    success(canvas.toDataURL(imgType, quality / 100.0));
                };
                shr.src = canvas.toDataURL();
            };
            img.src = res;
        };
        reader.readAsDataURL(file);
    }
};

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/files/modernizr.js version [6a3b0d2dd6].







>
>
>
1
2
3
/*! modernizr 3.3.1 (Custom Build) | MIT *
 * http://modernizr.com/download/?-webp-setclasses !*/
!function(e,n,A){function o(e,n){return typeof e===n}function t(){var e,n,A,t,a,i,l;for(var f in r)if(r.hasOwnProperty(f)){if(e=[],n=r[f],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(A=0;A<n.options.aliases.length;A++)e.push(n.options.aliases[A].toLowerCase());for(t=o(n.fn,"function")?n.fn():n.fn,a=0;a<e.length;a++)i=e[a],l=i.split("."),1===l.length?Modernizr[l[0]]=t:(!Modernizr[l[0]]||Modernizr[l[0]]instanceof Boolean||(Modernizr[l[0]]=new Boolean(Modernizr[l[0]])),Modernizr[l[0]][l[1]]=t),s.push((t?"":"no-")+l.join("-"))}}function a(e){var n=u.className,A=Modernizr._config.classPrefix||"";if(c&&(n=n.baseVal),Modernizr._config.enableJSClass){var o=new RegExp("(^|\\s)"+A+"no-js(\\s|$)");n=n.replace(o,"$1"+A+"js$2")}Modernizr._config.enableClasses&&(n+=" "+A+e.join(" "+A),c?u.className.baseVal=n:u.className=n)}function i(e,n){if("object"==typeof e)for(var A in e)f(e,A)&&i(A,e[A]);else{e=e.toLowerCase();var o=e.split("."),t=Modernizr[o[0]];if(2==o.length&&(t=t[o[1]]),"undefined"!=typeof t)return Modernizr;n="function"==typeof n?n():n,1==o.length?Modernizr[o[0]]=n:(!Modernizr[o[0]]||Modernizr[o[0]]instanceof Boolean||(Modernizr[o[0]]=new Boolean(Modernizr[o[0]])),Modernizr[o[0]][o[1]]=n),a([(n&&0!=n?"":"no-")+o.join("-")]),Modernizr._trigger(e,n)}return Modernizr}var s=[],r=[],l={_version:"3.3.1",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,n){var A=this;setTimeout(function(){n(A[e])},0)},addTest:function(e,n,A){r.push({name:e,fn:n,options:A})},addAsyncTest:function(e){r.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=l,Modernizr=new Modernizr;var f,u=n.documentElement,c="svg"===u.nodeName.toLowerCase();!function(){var e={}.hasOwnProperty;f=o(e,"undefined")||o(e.call,"undefined")?function(e,n){return n in e&&o(e.constructor.prototype[n],"undefined")}:function(n,A){return e.call(n,A)}}(),l._l={},l.on=function(e,n){this._l[e]||(this._l[e]=[]),this._l[e].push(n),Modernizr.hasOwnProperty(e)&&setTimeout(function(){Modernizr._trigger(e,Modernizr[e])},0)},l._trigger=function(e,n){if(this._l[e]){var A=this._l[e];setTimeout(function(){var e,o;for(e=0;e<A.length;e++)(o=A[e])(n)},0),delete this._l[e]}},Modernizr._q.push(function(){l.addTest=i}),Modernizr.addAsyncTest(function(){function e(e,n,A){function o(n){var o=n&&"load"===n.type?1==t.width:!1,a="webp"===e;i(e,a?new Boolean(o):o),A&&A(n)}var t=new Image;t.onerror=o,t.onload=o,t.src=n}var n=[{uri:"data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=",name:"webp"},{uri:"data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==",name:"webp.alpha"},{uri:"data:image/webp;base64,UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA",name:"webp.animation"},{uri:"data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=",name:"webp.lossless"}],A=n.shift();e(A.name,A.uri,function(A){if(A&&"load"===A.type)for(var o=0;o<n.length;o++)e(n[o].name,n[o].uri)})}),t(),a(s),delete l.addTest,delete l.addAsyncTest;for(var p=0;p<Modernizr._q.length;p++)Modernizr._q[p]();e.Modernizr=Modernizr}(window,document);

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/files/muon_bg.jpg version [6feb7231e4].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/files/muon_inputs.jpg version [1807ecef2c].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/files/muon_posts.jpg version [abe1959912].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/Icons.scala version [54c2b45b13].















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.karasiq.nanoboard.frontend

import com.karasiq.bootstrap.Bootstrap.default._

/**
  * Nanoboard application icons
  */
object Icons {
  @inline 
  private[this] def fa(name: String): IconModifier = name.fontAwesome(FontAwesome.fixedWidth)

  lazy val thread = fa("server")
  lazy val settings = fa("cogs")
  lazy val container = fa("globe")
  lazy val previous = fa("angle-double-left")
  lazy val next = fa("angle-double-right")
  lazy val removeContainer = fa("ban")
  lazy val batchDelete = fa("eraser")
  lazy val clearDeleted = fa("eye-slash")
  lazy val preferences = fa("wrench")
  lazy val control = fa("warning")
  lazy val containers = fa("archive")
  lazy val answers = fa("envelope-o")
  lazy val recent = fa("newspaper-o")
  lazy val categories = fa("sitemap")
  lazy val link = fa("link")
  lazy val parent = fa("level-up")
  lazy val delete = fa("trash-o")
  lazy val enqueue = fa("sign-in")
  lazy val dequeue = fa("sign-out")
  lazy val image = fa("file-image-o")
  lazy val video = fa("play-circle")
  lazy val file = fa("file-archive-o")
  lazy val submit = fa("mail-forward")
  lazy val reply = fa("reply")
  lazy val source = fa("file-text-o")
  lazy val music = fa("music")
  lazy val verify = fa("key")
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/NanoboardContext.scala version [cf63fbfde0].





























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.karasiq.nanoboard.frontend

import rx._

import com.karasiq.nanoboard.frontend.utils.RxLocation

sealed trait NanoboardContext
sealed trait NanoboardContextWithOffset extends NanoboardContext {
  def offset: Int
  def withOffset(newOffset: Int): NanoboardContextWithOffset
}
object NanoboardContext {
  case object Categories extends NanoboardContext
  case class Thread(hash: String, offset: Int = 0) extends NanoboardContextWithOffset {
    def withOffset(newOffset: Int) = copy(offset = newOffset)
  }
  case class Recent(offset: Int = 0) extends NanoboardContextWithOffset {
    def withOffset(newOffset: Int) = copy(offset = newOffset)
  }
  case class Pending(offset: Int = 0) extends NanoboardContextWithOffset {
    def withOffset(newOffset: Int) = copy(offset = newOffset)
  }

  //noinspection VariablePatternShadow
  // Simple single page app router
  def fromLocation()(implicit ctx: Ctx.Owner): Var[NanoboardContext] = {
    val location = RxLocation()
    val sha256 = "([a-fA-F0-9]{32})".r
    val sha256WithOffset = "([a-fA-F0-9]{32})/(\\d+)".r
    val onlyOffset = "(\\d+)".r
    val pendingOffset = "pending/(\\d+)".r
    val result = Var[NanoboardContext](NanoboardContext.Categories)
    location.hash.foreach { hash ⇒
      result() = hash match {
        case Some(sha256(hash)) ⇒
          NanoboardContext.Thread(hash)

        case Some(sha256WithOffset(hash, offset)) ⇒
          NanoboardContext.Thread(hash, offset.toInt)

        case Some(onlyOffset(offset)) ⇒
          NanoboardContext.Recent(offset.toInt)

        case Some("pending") ⇒
          NanoboardContext.Pending()

        case Some(pendingOffset(offset)) ⇒
          NanoboardContext.Pending(offset.toInt)

        case _ ⇒
          NanoboardContext.Categories
      }
    }

    result.triggerLater {
      location.hash() = result.now match {
        case NanoboardContext.Categories ⇒
          None

        case NanoboardContext.Thread(hash, 0) ⇒
          Some(s"$hash")

        case NanoboardContext.Thread(hash, offset) ⇒
          Some(s"$hash/$offset")

        case NanoboardContext.Recent(offset) ⇒
          Some(offset.toString)

        case NanoboardContext.Pending(0) ⇒
          Some("pending")

        case NanoboardContext.Pending(offset) ⇒
          Some(s"pending/$offset")
      }
    }
    result
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/NanoboardController.scala version [5ce3154fbd].

































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package com.karasiq.nanoboard.frontend

import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue

import org.scalajs.dom._
import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._

import com.karasiq.bootstrap.Bootstrap.default._
import com.karasiq.nanoboard.api.NanoboardMessageData
import com.karasiq.nanoboard.frontend.api.streaming.NanoboardMessageStream
import com.karasiq.nanoboard.frontend.components._
import com.karasiq.nanoboard.frontend.locales.BoardLocale
import com.karasiq.nanoboard.frontend.styles.BoardStyle
import com.karasiq.nanoboard.frontend.utils.Mouse
import com.karasiq.nanoboard.streaming.{NanoboardEvent, NanoboardSubscription}
import com.karasiq.nanoboard.streaming.NanoboardSubscription.{PostHashes, Unfiltered}

object NanoboardController {
  def apply(): NanoboardController = {
    new NanoboardController()
  }
}

final class NanoboardController {
  private implicit def controller: NanoboardController = this

  private val styleSelector = BoardStyle.selector

  val style: BoardStyle = styleSelector.style.now

  val locale = BoardLocale.fromBrowserLanguage()

  private val thread = ThreadContainer(NanoboardContext.fromLocation(), postsPerPage = 20)

  private val settingsPanel = SettingsPanel()

  private val pngGenerationPanel = PngGenerationPanel()

  private val title = ThreadPageTitle(thread.model)

  private val styleField = FormInput.simpleSelect(locale.style, BoardStyle.styles.map(_.toString):_*)

  styleField.selected() = Seq(style.toString)

  styleField.selected.map(_.head).foreach { style ⇒
    styleSelector.style() = BoardStyle.fromString(style)
  }

  private val navigationBar = NavigationBar()
    .withBrand("Nanoboard", onclick := Callback.onClick { _ ⇒
      setContext(NanoboardContext.Categories)
    })
    .withTabs(
      NavigationTab(locale.nanoboard, "thread", Icons.thread, GridSystem.containerFluid(thread)),
      NavigationTab(locale.settings, "server-settings", Icons.settings, GridSystem.container(
        GridSystem.mkRow(styleField.renderTag(style.input)),
        GridSystem.mkRow(settingsPanel)
      )),
      NavigationTab(locale.containerGeneration, "png-gen", Icons.container, GridSystem.container(GridSystem.mkRow(pngGenerationPanel)))
    )
    .withStyles(NavigationBarStyle.staticTop, NavigationBarStyle.inverse)
    .withContentContainer(md ⇒ div(md))
    .build()

  private val messageChannel = NanoboardMessageStream {
    case NanoboardEvent.PostAdded(message) ⇒
      // Notifications.info(s"New message: ${message.text}", Layout.topRight)
      addPost(message)

    case NanoboardEvent.PostDeleted(hash) ⇒
      // Notifications.warning(s"Post was deleted: $hash", Layout.topRight)
      deleteSingle(NanoboardMessageData(None, None, hash, "", 0))

    case NanoboardEvent.PostVerified(message) ⇒
      addPending(message)
  }

  def initialize(): Unit = {
    document.head.appendChild(controller.title.renderTag().render)
    Seq[Modifier](navigationBar, style.body, controller.styleSelector.renderTag(id := "nanoboard-style"))
      .foreach(_.applyTo(document.body))
  }

  def updateCategories(): Unit = {
    thread.model.updateCategories()
  }

  def updatePosts(): Unit = {
    Seq(thread.model, pngGenerationPanel.model).foreach(_.updatePosts())
  }

  def showPost(hash: String): Unit = {
    if (!Mouse.scroll(s"#post-$hash")) {
      setContext(NanoboardContext.Thread(hash))
    }
  }

  def setContext(context: NanoboardContext): Unit = {
    thread.context() = context
    navigationBar.selectTab("thread")
  }

  def isPending(hash: String): Rx[Boolean] = {
    pngGenerationPanel.model.posts.map(_.exists(_.hash == hash))
  }

  def addPending(post: NanoboardMessageData): Unit = {
    pngGenerationPanel.model.addPost(post)
  }

  def deletePending(post: NanoboardMessageData): Unit = {
    pngGenerationPanel.model.deleteSingle(post)
  }

  def addPost(post: NanoboardMessageData): Unit = {
    thread.model.addPost(post)
  }

  def deleteSingle(post: NanoboardMessageData): Unit = {
    Seq(thread.model, pngGenerationPanel.model).foreach(_.deleteSingle(post))
  }

  private val messageChannelContext = Rx[NanoboardSubscription] {
    thread.model.context() match {
      case NanoboardContext.Recent(0) | NanoboardContext.Pending(0) ⇒
        // Accept all posts
        Unfiltered

      case context ⇒
        PostHashes(thread.model.posts().map(_.hash).toSet ++ thread.model.categories().map(_.hash).toSet ++ Some(context).collect {
          case NanoboardContext.Thread(hash, _) ⇒
            hash
        })
    }
  }

  messageChannelContext.foreach { context ⇒
    messageChannel.setContext(context)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/NanoboardFrontend.scala version [024f582129].







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.karasiq.nanoboard.frontend

import scala.language.postfixOps

import moment.Moment
import org.scalajs.jquery.jQuery

import com.karasiq.nanoboard.frontend.locales.BoardLocale
import com.karasiq.taboverridejs.TabOverride

object NanoboardFrontend {
  def main(args: Array[String]): Unit = {
    jQuery(() ⇒ {
      TabOverride.tabSize(2)
      Moment.locale(BoardLocale.browserLanguage)
      NanoboardController().initialize()
    })
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/api/BinaryMarshaller.scala version [dcc11c90de].







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.karasiq.nanoboard.frontend.api

import java.nio.ByteBuffer

import scala.scalajs.js.typedarray.{ArrayBuffer, TypedArrayBuffer}

import boopickle.Default._

private[api] object BinaryMarshaller {
  def responseType: String = "arraybuffer"

  def write[T: Pickler](value: T): ByteBuffer = {
    Pickle.intoBytes(value)
  }

  def read[T: Pickler](value: Any): T = {
    Unpickle[T].fromBytes(TypedArrayBuffer.wrap(value.asInstanceOf[ArrayBuffer]))
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/api/NanoboardApi.scala version [79a4fedb0b].













































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.karasiq.nanoboard.frontend.api


import scala.concurrent.Future
import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue

import boopickle.Default._
import org.scalajs.dom.{console, Blob}
import org.scalajs.dom.ext.Ajax
import org.scalajs.dom.raw.XMLHttpRequest

import com.karasiq.nanoboard.api._

/**
  * Nanoboard REST API
  */
object NanoboardApi {
  private val marshaller = BinaryMarshaller
  
  private def readResponse[T: Pickler](response: XMLHttpRequest): T = {
    if (response.status == 200) {
      marshaller.read[T](response.response)
    } else {
      val message = s"Nanoboard API error: ${response.status} ${response.statusText}"
      console.error(message)
      throw new IllegalArgumentException(message)
    }
  }

  def categories(): Future[Vector[NanoboardMessageData]] = {
    Ajax.get("/categories", responseType = marshaller.responseType)
      .map(readResponse[Vector[NanoboardMessageData]])
  }

  def post(hash: String): Future[Option[NanoboardMessageData]] = {
    Ajax.get(s"/post/$hash", responseType = marshaller.responseType)
      .map(readResponse[Option[NanoboardMessageData]])
  }

  def thread(hash: String, offset: Long, count: Long): Future[Vector[NanoboardMessageData]] = {
    Ajax.get(s"/posts/$hash?offset=$offset&count=$count", responseType = marshaller.responseType)
      .map(readResponse[Vector[NanoboardMessageData]])
  }

  def addReply(hash: String, message: String): Future[NanoboardMessageData] = {
    Ajax.post(s"/post", marshaller.write(NanoboardReply(hash, message)), responseType = marshaller.responseType)
      .map(readResponse[NanoboardMessageData])
  }

  def markAsPending(hash: String): Future[Unit] = {
    Ajax.put(s"/pending/$hash").map(_ ⇒ ())
  }

  def markAsNotPending(hash: String): Future[Unit] = {
    Ajax.delete(s"/pending/$hash").map(_ ⇒ ())
  }

  def delete(hash: String): Future[Seq[String]] = {
    Ajax.delete(s"/post/$hash", responseType = marshaller.responseType)
      .map(readResponse[Seq[String]])
  }

  def clearDeleted(): Future[Int] = {
    Ajax.delete("/deleted", responseType = marshaller.responseType)
      .map(readResponse[Int])
  }

  def delete(offset: Int, count: Int): Future[Seq[String]] = {
    Ajax.delete(s"/posts?offset=$offset&count=$count", responseType = marshaller.responseType)
      .map(readResponse[Seq[String]])
  }

  def places(): Future[Seq[String]] = {
    Ajax.get("/places", responseType = marshaller.responseType)
      .map(readResponse[Seq[String]])
  }

  def setPlaces(newList: Seq[String]): Future[Unit] = {
    Ajax.put("/places", marshaller.write(newList))
      .map(_ ⇒ ())
  }

  def setCategories(newList: Seq[NanoboardCategory]): Future[Unit] = {
    Ajax.put("/categories", marshaller.write(newList))
      .map(_ ⇒ ())
  }

  def pending(offset: Long, count: Long): Future[Vector[NanoboardMessageData]] = {
    Ajax.get(s"/pending?offset=$offset&count=$count", responseType = marshaller.responseType)
      .map(readResponse[Vector[NanoboardMessageData]])
  }

  def recent(offset: Long, count: Long): Future[Vector[NanoboardMessageData]] = {
    Ajax.get(s"/posts?offset=$offset&count=$count", responseType = marshaller.responseType)
      .map(readResponse[Vector[NanoboardMessageData]])
  }

  def generateContainer(pending: Int, random: Int, format: String, container: Ajax.InputData): Future[Blob] = {
    Ajax.post(s"/container?pending=$pending&random=$random&format=$format", container, responseType = "blob")
      .map { r ⇒
        if (r.status == 200) {
          r.response.asInstanceOf[Blob]
        } else {
          throw new IllegalArgumentException(s"${r.status} ${r.statusText}")
        }
      }
  }

  def generateAttachment(format: String, size: Int, quality: Int, container: Ajax.InputData): Future[String] = {
    Ajax.post(s"/attachment?format=$format&size=$size&quality=$quality", container)
      .map(_.responseText)
  }

  def containers(offset: Long, count: Long): Future[Vector[NanoboardContainer]] = {
    Ajax.get(s"/containers?offset=$offset&count=$count", responseType = marshaller.responseType)
      .map(readResponse[Vector[NanoboardContainer]])
  }

  def clearContainer(id: Long): Future[Vector[String]] = {
    Ajax.delete(s"/posts?container=$id", responseType = marshaller.responseType)
      .map(readResponse[Vector[String]])
  }

  def requestVerification(hash: String): Future[NanoboardCaptchaRequest] = {
    Ajax.get(s"/verify/$hash", responseType = marshaller.responseType)
      .map(readResponse[NanoboardCaptchaRequest])
  }

  def verifyPost(request: NanoboardCaptchaRequest, answer: String): Future[NanoboardMessageData] = {
    Ajax.post(s"/verify", marshaller.write(NanoboardCaptchaAnswer(request, answer)), responseType = marshaller.responseType)
      .map(readResponse[NanoboardMessageData])
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/api/streaming/NanoboardMessageStream.scala version [78920887fb].









































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.karasiq.nanoboard.frontend.api.streaming

import java.nio.ByteBuffer

import scala.scalajs.js.typedarray.{ArrayBuffer, TypedArrayBuffer}
import scala.scalajs.js.typedarray.TypedArrayBufferOps._

import boopickle.Default._
import org.scalajs.dom.window
import org.scalajs.dom.raw._

import com.karasiq.nanoboard.frontend.NanoboardController
import com.karasiq.nanoboard.frontend.api.BinaryMarshaller
import com.karasiq.nanoboard.frontend.utils.Notifications
import com.karasiq.nanoboard.frontend.utils.Notifications.Layout
import com.karasiq.nanoboard.streaming.{NanoboardEvent, NanoboardEventSeq, NanoboardSubscription}
import com.karasiq.nanoboard.streaming.NanoboardSubscription.Unfiltered

object NanoboardMessageStream {
  def apply(f: PartialFunction[NanoboardEvent, Unit])(implicit controller: NanoboardController): NanoboardMessageStream = {
    new NanoboardMessageStream(f)
  }

  private[api] def asArrayBuffer(data: ByteBuffer): ArrayBuffer = {
    if (data.hasTypedArray()) {
      data.typedArray().subarray(data.position, data.limit).asInstanceOf[ArrayBuffer]
    } else {
      val tempBuffer = ByteBuffer.allocateDirect(data.remaining)
      val origPosition = data.position
      tempBuffer.put(data)
      data.position(origPosition)
      tempBuffer.typedArray().asInstanceOf[ArrayBuffer]
    }
  }

  private[api] def asEventSeq(response: Any): NanoboardEventSeq = {
    Unpickle[NanoboardEventSeq].fromBytes(TypedArrayBuffer.wrap(response.asInstanceOf[ArrayBuffer]))
  }
}

// WebSocket wrapper
final class NanoboardMessageStream(f: PartialFunction[NanoboardEvent, Unit])(implicit controller: NanoboardController) {
  private var webSocket: Option[WebSocket] = None
  private var last: NanoboardSubscription = Unfiltered
  private var lastSet = false

  def setContext(context: NanoboardSubscription): Unit = {
    if (!lastSet || context != last) {
      webSocket.foreach { webSocket ⇒
        val buffer = NanoboardMessageStream.asArrayBuffer(BinaryMarshaller.write(context))
        webSocket.send(buffer)
      }
    }
    last = context
    lastSet = webSocket.isDefined
  }

  private def initWebSocket(): Unit = {
    val webSocket = new WebSocket(s"ws://${window.location.host}/live")

    webSocket.binaryType = "arraybuffer"

    webSocket.onmessage = { (m: MessageEvent) ⇒
      val eventSeq = NanoboardMessageStream.asEventSeq(m.data)
      eventSeq.events
        .filter(f.isDefinedAt)
        .foreach(f(_))
    }

    webSocket.onclose = { (e: CloseEvent) ⇒
      this.webSocket = None
      Notifications.warning(s"${controller.locale.webSocketError}: ${e.code} ${e.reason}", Layout.topRight)
      window.setTimeout(() ⇒ initWebSocket(), 3000)
    }

    webSocket.onopen = { (e: Event) ⇒
      this.lastSet = false
      this.webSocket = Some(webSocket)
      setContext(last)
    }
  }

  initWebSocket()
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/ContainersPanel.scala version [ea8801237f].













































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.karasiq.nanoboard.frontend.components

import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.util.{Failure, Success}

import moment.Moment
import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._

import com.karasiq.nanoboard.api.NanoboardContainer
import com.karasiq.nanoboard.frontend.{Icons, NanoboardController}
import com.karasiq.nanoboard.frontend.api.NanoboardApi
import com.karasiq.nanoboard.frontend.utils.Notifications
import com.karasiq.nanoboard.frontend.utils.Notifications.Layout

private[components] object ContainersPanel {
  def apply(perPage: Int)(implicit controller: NanoboardController): ContainersPanel = {
    new ContainersPanel(perPage)
  }
}

private[components] final class ContainersPanel(perPage: Int)(implicit controller: NanoboardController)
  extends BootstrapHtmlComponent {

  import controller.locale
  val currentOffset = Var(0)
  val containers = Var(Vector.empty[NanoboardContainer])

  def update(): Unit = {
    NanoboardApi.containers(currentOffset.now, perPage).foreach { cs ⇒
      containers() = cs
    }
  }

  currentOffset.foreach(_ ⇒ update())

  def renderTag(md: Modifier*) = {
    def isEmpty(c: NanoboardContainer): Modifier = if (c.posts == 0) Seq(textDecoration.`line-through`, color.gray) else ()
    def dateTime(c: NanoboardContainer): Modifier = Moment(c.time.toDouble).format("YYYY, MMMM Do, HH:mm")
    var loading = false
    div(containers.map { cs ⇒
      div(
        GridSystem.mkRow(
          ButtonGroup(ButtonGroupSize.extraSmall,
            Button(ButtonStyle.danger)(
              Icons.previous,
              locale.fromTo(math.max(0, currentOffset.now - perPage), currentOffset.now),
              onclick := Callback.onClick { _ ⇒
                currentOffset() = math.max(0, currentOffset.now - perPage)
              }
            ),
            Button(ButtonStyle.success)(
              locale.fromTo(currentOffset.now + containers.now.length, currentOffset.now + containers.now.length + perPage),
              Icons.next,
              onclick := Callback.onClick { _ ⇒
                currentOffset() = currentOffset.now + containers.now.length
              }
            )
          )
        ),
        for (c ← cs) yield GridSystem.mkRow(isEmpty(c), a(href := "#", Icons.removeContainer, onclick := Callback.onClick { _ ⇒
          if (!loading) {
            Notifications.confirmation(locale.batchDeleteConfirmation(c.posts), Layout.topLeft) {
              loading = true
              NanoboardApi.clearContainer(c.id).onComplete {
                case Success(_) ⇒
                  loading = false
                  this.update()
                  controller.updatePosts()
                  controller.updateCategories()
                  Notifications.success(locale.batchDeleteSuccess(c.posts), Layout.topRight)

                case Failure(exc) ⇒
                  loading = false
                  Notifications.error(exc)(locale.batchDeleteError, Layout.topRight)
              }
            }
          }
        }), dateTime(c), " ", s"${locale.container(c)} (", a(isEmpty(c), href := c.url, c.url, target := "_blank"), ")")
      )
    })
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/PngGenerationPanel.scala version [06a3bdaa78].





















































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.karasiq.nanoboard.frontend.components

import scala.concurrent.ExecutionContext
import scala.scalajs.js
import scala.util.{Failure, Success}

import org.scalajs.dom.Blob
import org.scalajs.dom.html.Input
import rx._
import scalatags.JsDom.all._

import com.karasiq.bootstrap.Bootstrap.default._
import com.karasiq.nanoboard.frontend.{NanoboardContext, NanoboardController}
import com.karasiq.nanoboard.frontend.api.NanoboardApi
import com.karasiq.nanoboard.frontend.components.post.NanoboardPost
import com.karasiq.nanoboard.frontend.model.ThreadModel
import com.karasiq.nanoboard.frontend.utils.{Blobs, Notifications}
import com.karasiq.nanoboard.frontend.utils.Notifications.Layout

object PngGenerationPanel {
  def apply()(implicit ec: ExecutionContext, controller: NanoboardController): PngGenerationPanel = {
    new PngGenerationPanel
  }
}

final class PngGenerationPanel(implicit ec: ExecutionContext, controller: NanoboardController) extends BootstrapHtmlComponent {
  import controller.{locale, style}

  val model = ThreadModel(Var(NanoboardContext.Pending()), 100)

  private val loading = Var(false)

  private val pendingContainer = Rx[Frag] {
    val posts = model.posts()
    if (posts.nonEmpty) div(
      marginTop := 20.px,
      h3(locale.pendingPosts),
      for (p ← posts) yield GridSystem.mkRow(NanoboardPost(showParent = true, showAnswers = false, p))
    ) else ()
  }

  private val form = Form(
    FormInput.number(locale.pendingPosts, style.input, name := "pending", value := 3, min := 0),
    FormInput.number(locale.randomPosts, style.input, name := "random", value := 30, min := 0),
    // FormInput.text(locale.imageFormat, style.input, name := "format", value := "png"),
    FormInput.file(locale.dataContainer, style.input, name := "container"),
    Form.submit(locale.generateContainer)(style.submit, "disabled".classIf(loading), "btn-block".addClass),
    onsubmit := Callback.onSubmit { frm ⇒
      if (!loading.now) {
        loading() = true
        def input(name: String) = frm(name).asInstanceOf[Input]
        val file: Blob = input("container").files.headOption.getOrElse(Blobs.fromBytes(Array.emptyByteArray))
        val pending = input("pending").valueAsNumber
        val random = input("random").valueAsNumber
        val format = "png" // input("format").value

        NanoboardApi.generateContainer(pending, random, format, file).onComplete {
          case Success(blob) ⇒
            loading() = false
            Blobs.saveBlob(blob, s"${js.Date.now()}.$format")
            model.updatePosts()

          case Failure(exc) ⇒
            loading() = false
            Notifications.error(exc)(locale.containerGenerationError, Layout.topRight, 1500)
        }
      }
    }
  )

  override def renderTag(md: Modifier*) = {
    div(form, pendingContainer, marginBottom := 50.px)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/SettingsPanel.scala version [acf1a59288].































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package com.karasiq.nanoboard.frontend.components

import scala.concurrent.Future
import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.util.{Failure, Success, Try}

import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._

import com.karasiq.nanoboard.api.NanoboardCategory
import com.karasiq.nanoboard.frontend.{Icons, NanoboardController}
import com.karasiq.nanoboard.frontend.api.NanoboardApi
import com.karasiq.nanoboard.frontend.utils.Notifications
import com.karasiq.nanoboard.frontend.utils.Notifications.Layout

object SettingsPanel {
  def apply()(implicit controller: NanoboardController): SettingsPanel = {
    new SettingsPanel
  }
}

final class SettingsPanel(implicit controller: NanoboardController) extends BootstrapHtmlComponent {
  import controller.{locale, style}

  private val placesText = Var("")
  private val categoriesText = Var("")

  val places = Rx {
    val urls = placesText().lines.toVector
    if (urls.forall(_.matches("""\b(https?|ftp)://([-a-zA-Z0-9.]+)(/[-a-zA-Z0-9+&@#/%=~_|!:,.;]*)?(\?[a-zA-Z0-9+&@#/%=~_|!:,.;]*)?"""))) {
      urls
    } else {
      Vector.empty
    }
  }

  val categories = Rx {
    Try {
      val lines = categoriesText().lines.toVector
      require(lines.length % 2 == 0)

      val categories = lines.grouped(2).map(seq ⇒ NanoboardCategory(seq.head, seq.last)).toVector
      require(categories.forall(c ⇒ c.hash.matches("[a-fA-F0-9]{32}") && c.name.nonEmpty))

      categories
    }.getOrElse(Vector.empty)
  }

  private val loading = Var(false)

  private val buttonDisabled = Rx {
    loading() || categories().isEmpty || places().isEmpty
  }

  override def renderTag(md: Modifier*) = {
    val batchDelete = {
      val offset, count = Var("0")
      val loading = Var(false)
      val disabled = Rx {
        loading() || Try(offset().toInt).filter(_ >= 0).isFailure || Try(count().toInt).filter(_ > 0).isFailure
      }
      Form(
        FormInput.number(locale.offset, style.input, min := 0, offset.reactiveInput),
        FormInput.number(locale.count, style.input, min := 0, count.reactiveInput),
        Button(ButtonStyle.danger, block = true)(Icons.batchDelete, locale.batchDelete, "disabled".classIf(disabled), onclick := Callback.onClick { _ ⇒
          if (!disabled.now) {
            Notifications.confirmation(locale.batchDeleteConfirmation(count.now.toInt), Layout.topLeft) {
              loading() = true
              NanoboardApi.delete(offset.now.toInt, count.now.toInt).onComplete {
                case Success(hashes) ⇒
                  controller.updatePosts()
                  controller.updateCategories()
                  count() = ""
                  loading() = false
                  Notifications.success(locale.batchDeleteSuccess(hashes.length), Layout.topRight)

                case Failure(exc) ⇒
                  loading() = false
                  Notifications.error(exc)(locale.batchDeleteError, Layout.topRight)
              }
            }
          }
        })
      )
    }

    val clearDeleted = {
      val loading = Var(false)
      Button(ButtonStyle.warning, block = true)(Icons.clearDeleted, locale.clearDeleted, "disabled".classIf(loading), onclick := Callback.onClick { _ ⇒
        if (!loading.now) {
          Notifications.confirmation(locale.clearDeletedConfirmation, Layout.topLeft) {
            loading() = true
            NanoboardApi.clearDeleted().onComplete {
              case Success(count) ⇒
                loading() = false
                Notifications.success(locale.clearDeletedSuccess(count), Layout.topRight)

              case Failure(exc) ⇒
                loading() = false
                Notifications.error(exc)(locale.clearDeletedError, Layout.topRight)
            }
          }
        }
      })
    }

    val navigation = Navigation.pills(
      NavigationTab(locale.preferences, "server", Icons.preferences, div(
        GridSystem.mkRow(Form(
          FormInput.textArea(locale.places, style.input, rows := 15, placesText.reactiveInput)("has-error".classIf(places.map(_.isEmpty))),
          FormInput.textArea(locale.categories, style.input, rows := 15, categoriesText.reactiveInput)("has-error".classIf(categories.map(_.isEmpty)))
        )),
        GridSystem.mkRow(Button(block = true)(locale.submit, style.submit, "disabled".classIf(buttonDisabled), onclick := Callback.onClick { _ ⇒
          if (!buttonDisabled.now) {
            loading() = true
            Future.sequence(Seq(NanoboardApi.setCategories(categories.now), NanoboardApi.setPlaces(places.now))).onComplete {
              case Success(_) ⇒
                loading() = false
                controller.updateCategories()

              case Failure(exc) ⇒
                Notifications.error(exc)(locale.settingsUpdateError, Layout.topRight)
                loading() = false
            }
          }
        }))
      )),
      NavigationTab(locale.control, "control", Icons.control, div(
        GridSystem.mkRow(h3(locale.batchDelete)),
        GridSystem.mkRow(batchDelete),
        GridSystem.mkRow(h3(locale.clearDeleted)),
        GridSystem.mkRow(clearDeleted)
      )),
      NavigationTab(locale.containers, "containers", Icons.containers, div(
        ContainersPanel(30)
      ))
    )

    div(
      navigation,
      marginBottom := 50.px
    )
  }

  def update(): Unit = {
    NanoboardApi.places().foreach { places ⇒
      placesText() = places.mkString("\n")
    }

    NanoboardApi.categories().foreach { categories ⇒
      categoriesText() = categories.map(c ⇒ s"${c.hash}\n${c.text}").mkString("\n")
    }
  }

  update()
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/ThreadContainer.scala version [e4afb76145].

































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package com.karasiq.nanoboard.frontend.components

import scala.language.postfixOps

import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._

import com.karasiq.nanoboard.api.NanoboardMessageData
import com.karasiq.nanoboard.frontend.{Icons, NanoboardContext, NanoboardContextWithOffset, NanoboardController}
import com.karasiq.nanoboard.frontend.components.post.{NanoboardPost, PostRenderer}
import com.karasiq.nanoboard.frontend.model.ThreadModel
import com.karasiq.nanoboard.frontend.utils.PostParser

object ThreadContainer {
  def apply(context: Var[NanoboardContext], postsPerPage: Int)
           (implicit controller: NanoboardController): ThreadContainer = {
    new ThreadContainer(context, postsPerPage)
  }
}

final class ThreadContainer(val context: Var[NanoboardContext], postsPerPage: Int)
                           (implicit controller: NanoboardController) extends BootstrapHtmlComponent {
  import controller.locale

  val model = ThreadModel(context, postsPerPage)

  // View
  private val threadPosts = Rx[Frag] {
    val thread = model.posts()
    val rendered = context.now match {
      case NanoboardContext.Thread(hash, _) ⇒
        val (opPost, answers) = thread.partition(_.hash == hash)
        opPost.map(NanoboardPost(true, false, _, scrollable = true)) ++ answers.map(NanoboardPost(false, true, _, scrollable = true))

      case NanoboardContext.Recent(_) | NanoboardContext.Pending(_) ⇒
        thread.map(NanoboardPost(true, true, _, scrollable = true))

      case NanoboardContext.Categories ⇒
        thread.map(NanoboardPost(false, true, _, scrollable = true))
    }
    div(for (p ← rendered) yield GridSystem.mkRow(p))
  }

  private val pagination = Rx[Frag] {
    val posts = model.posts()
    val deleted = model.deletedPosts().size

    def previousButton(ofs: NanoboardContextWithOffset, prevOffset: Int): Tag  = {
      Button(ButtonStyle.danger)(
        Icons.previous,
        locale.fromTo(prevOffset, prevOffset + postsPerPage),
        onclick := Callback.onClick { _ ⇒
          context() = ofs.withOffset(prevOffset)
        })
    }

    def nextButton(ofs: NanoboardContextWithOffset, newOffset: Int): Tag = {
      Button(ButtonStyle.success)(
        locale.fromTo(newOffset, newOffset + postsPerPage),
        Icons.next,
        onclick := Callback.onClick { _ ⇒
          context() = ofs.withOffset(math.max(0, newOffset))
        })
    }

    context.now match {
      case NanoboardContext.Categories ⇒
        ""

      case th @ NanoboardContext.Thread(hash, offset) ⇒
        ButtonGroup(ButtonGroupSize.default,
          if (offset > 0) previousButton(th, math.max(0, offset - postsPerPage)) else (),
          if ((posts.length + deleted - 1) >= postsPerPage) nextButton(th, offset + math.max(0, posts.length - 1)) else (),
          margin := 5.px
        )

      case ofs: NanoboardContextWithOffset ⇒
        ButtonGroup(ButtonGroupSize.default,
          if (ofs.offset > 0) previousButton(ofs, math.max(0, ofs.offset - postsPerPage)) else (),
          if ((posts.length + deleted) >= postsPerPage) nextButton(ofs, ofs.offset + posts.length) else (),
          margin := 5.px
        )
    }
  }

  override def renderTag(md: Modifier*): TagT = {
    val categories = Rx[Frag] {
      span(
        model.categories().map[Frag, Seq[Frag]] {
          case m @ NanoboardMessageData(_, _, hash, _, answers, _, _) ⇒
            val plainText = PostRenderer.strip(PostParser.parse(m.text))
            val answersSpan = span(marginLeft := 0.25.em, Icons.answers, answers)
            a(href := s"#$hash", margin := 0.25.em, "[", span(fontWeight.bold, plainText), answersSpan, "]", onclick := Callback.onClick { _ ⇒
              controller.setContext(NanoboardContext.Thread(hash))
            })
        }
      )
    }

    val navigation = Seq(
      a(href := "#0", margin := 0.25.em, Icons.recent, locale.recentPosts, onclick := Callback.onClick { _ ⇒
        controller.setContext(NanoboardContext.Recent())
      }),
      a(href := "#", margin := 0.25.em, Icons.categories, locale.categories, onclick := Callback.onClick { _ ⇒
        controller.setContext(NanoboardContext.Categories)
      })
    )
    div(GridSystem.mkRow(categories), GridSystem.mkRow(navigation), GridSystem.mkRow(pagination), GridSystem.mkRow(threadPosts), GridSystem.mkRow(pagination), marginBottom := 400.px, md)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/ThreadPageTitle.scala version [cc9f1732a3].





































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.karasiq.nanoboard.frontend.components

import rx._
import scalatags.JsDom.tags2
import scalatags.JsDom.all._

import com.karasiq.bootstrap.Bootstrap.default._
import com.karasiq.nanoboard.frontend.{NanoboardContext, NanoboardController}
import com.karasiq.nanoboard.frontend.components.post.PostRenderer
import com.karasiq.nanoboard.frontend.model.ThreadModel
import com.karasiq.nanoboard.frontend.utils.PostParser

object ThreadPageTitle {
  def apply(thread: ThreadModel)(implicit controller: NanoboardController): ThreadPageTitle = {
    new ThreadPageTitle(thread)
  }
}

private[components] final class ThreadPageTitle(thread: ThreadModel)(implicit controller: NanoboardController) extends BootstrapHtmlComponent {
  import controller.locale

  val title = Rx[String] {
    thread.context() match {
      case NanoboardContext.Thread(_, _) ⇒
        thread.posts().headOption.fold(locale.nanoboard) { post ⇒
          val text = Some(PostParser.parse(post.text))
            .map(PostRenderer.strip(_).trim.split("\\s+").take(10).mkString(" "))
            .filter(_.nonEmpty)
          text.fold(locale.nanoboard)(locale.nanoboard + " - " + _)
        }

      case NanoboardContext.Recent(0) ⇒
        s"${locale.nanoboard} - ${locale.recentPosts}"

      case NanoboardContext.Recent(offset) ⇒
        s"${locale.nanoboard} - ${locale.recentPostsFrom(offset)}"

      case NanoboardContext.Categories ⇒
        s"${locale.nanoboard} - ${locale.categories}"

      case _ ⇒
        locale.nanoboard
    }

  }

  override def renderTag(md: Modifier*) = {
    tags2.title(title, md)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/Linkifier.scala version [ac97ade349].







































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.karasiq.nanoboard.frontend.components.post

import scala.language.postfixOps
import scala.util.matching.Regex

import org.scalajs.dom.Element

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._

import com.karasiq.nanoboard.frontend.{Icons, NanoboardController}
import com.karasiq.videojs.VideoSource

sealed trait LinkifierNode extends Frag
case class InlineDom(frag: Frag) extends LinkifierNode {
  override def render = frag.render
  override def applyTo(t: Element) = frag.applyTo(t)
}
case class InlineText(str: String) extends LinkifierNode {
  private val frag: Frag = str
  override def render = frag.render
  override def applyTo(t: Element) = frag.applyTo(t)
}

object Linkifier {
  private val videoRegex = """\b(https?|ftp)://([-a-zA-Z0-9.]+)(/[-a-zA-Z0-9+&@#/%=~_|!:,.;]*\.(webm|mp4|ogv|3gp|avi|mov))(\?[a-zA-Z0-9+&@#/%=~_|!:,.;]*)?""".r
  private val youtubeRegex = """https?://(?:www\.)?youtu(?:be\.com/watch\?v=|\.be/)([\w\-]+)(&(amp;)?[\w\?=]*)?""".r
  private val urlRegex = """\b(?:(?:https?|ftp|file)://|www\.|ftp\.)[-а-яА-Яa-zA-Z0-9+&@#/%=~_|$?!:,.]*[а-яА-ЯA-Za-z0-9+&@#/%=~_|$]""".r
  private val postLinkRegex = """(?:>>|/expand/)([A-Za-z0-9]{32})""".r
  private val quoteRegex = """(^|\n)>[^\r\n]+""".r

  private def processText(text: String, regex: Regex, f: String ⇒ Frag): Seq[LinkifierNode] = {
    regex.findFirstMatchIn(text) match {
      case Some(rm) ⇒
        val matched = text.slice(rm.start, rm.end)
        val texts = Seq(text.take(rm.start), text.drop(rm.end))
          .flatMap(processText(_, regex, f))
        texts.take(1) ++ Seq(InlineDom(f(matched))) ++ texts.drop(1)

      case None ⇒
        Seq(InlineText(text))
    }
  }

  def inlineYoutube(text: String): Seq[LinkifierNode] = {
    processText(text, youtubeRegex, url ⇒ PostExternalVideo.youtube(url))
  }

  def inlineVideos(text: String): Seq[LinkifierNode] = {
    processText(text, videoRegex, {
      case url @ videoRegex(protocol, domain, file, ext, query) ⇒
        PostExternalVideo(url, VideoSource(s"video/$ext", url))
    })
  }

  def linkify(text: String): Seq[LinkifierNode] = {
    processText(text, urlRegex, url ⇒ a(href := url, url))
  }

  def postLinks(text: String)(implicit controller: NanoboardController): Seq[LinkifierNode] = {
    processText(text, postLinkRegex, {
      case postLinkRegex(hash) ⇒
        PostLink(hash).renderTag(Icons.link, hash)
    })
  }

  def quotes(text: String)(implicit controller: NanoboardController): Seq[LinkifierNode] = {
    processText(text, quoteRegex, quote ⇒ span(controller.style.greenText, quote))
  }

  def apply(text: String)(implicit controller: NanoboardController): Seq[LinkifierNode] = {
    Seq(inlineYoutube _, inlineVideos _, linkify _, postLinks _, quotes _).foldLeft(Seq[LinkifierNode](InlineText(text))) {
      case (nodes, f) ⇒
        nodes.flatMap {
          case dom @ InlineDom(_) ⇒
            Some(dom)

          case InlineText(data) ⇒
            f(data)
        }
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/NanoboardPost.scala version [38865b2329].



















































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package com.karasiq.nanoboard.frontend.components.post

import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue

import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._

import com.karasiq.nanoboard.api.NanoboardMessageData
import com.karasiq.nanoboard.frontend.{Icons, NanoboardContext, NanoboardController}
import com.karasiq.nanoboard.frontend.api.NanoboardApi
import com.karasiq.nanoboard.frontend.components.post.actions.{PendingButton, ReplyField, VerificationButton}
import com.karasiq.nanoboard.frontend.styles.CommonStyles
import com.karasiq.nanoboard.frontend.utils.{Notifications, PostParser}
import com.karasiq.nanoboard.frontend.utils.Notifications.Layout

private[components] object NanoboardPost {
  def render(text: String)(implicit controller: NanoboardController): Frag = {
    PostRenderer().render(PostParser.parse(text))
  }

  def apply(showParent: Boolean, showAnswers: Boolean, data: NanoboardMessageData, scrollable: Boolean = false)
           (implicit controller: NanoboardController): NanoboardPost = {
    new NanoboardPost(showParent, showAnswers, data, scrollable)
  }
}

private[components] final class NanoboardPost(showParent: Boolean, showAnswers: Boolean, postData: NanoboardMessageData, scrollable: Boolean)
                                             (implicit controller: NanoboardController) extends BootstrapHtmlComponent {
  import controller.{locale, style}

  val expanded = Var(false)
  val showSource = Var(false)

  override def renderTag(md: Modifier*): TagT = {
    val heightMod = Rx[Modifier] {
      if (expanded())
        maxHeight := 100.pct
      else
        maxHeight := 48.em
    }

    div(
      if (scrollable) id := s"post-${postData.hash}" else (),
      style.post,
      div(
        heightMod.auto,
        style.postInner,
        CommonStyles.flatScroll,
        span(
          style.postId,
          if (showParent && postData.parent.isDefined) PostLink(postData.parent.get).renderTag(Icons.parent) else (),
          sup(cursor.pointer, postData.containerId.fold(postData.hash)(cid ⇒ s"${postData.hash}/$cid"), onclick := Callback.onClick(_ ⇒ expanded() = !expanded.now))
        ),
        Rx[Frag](if (showSource()) postData.text else span(NanoboardPost.render(postData.text)))
      ),
      div(
        if (showAnswers && postData.answers > 0) a(style.postLink, href := s"#${postData.hash}", Icons.answers, s"${postData.answers}", onclick := Callback.onClick { _ ⇒
          this.openAsThread()
        }) else (),
        a(style.postLink, href := "#", Icons.delete, locale.delete, onclick := Callback.onClick { _ ⇒
          this.delete()
        }),
        PendingButton(postData),
        a(style.postLink, href := "#", Icons.source, locale.source, onclick := Callback.onClick(_ ⇒ showSource() = !showSource.now)),
        VerificationButton(postData),
        ReplyField(postData)
      ),
      md
    )
  }

  def openAsThread(): Unit = {
    controller.setContext(NanoboardContext.Thread(postData.hash, 0))
  }

  def delete(): Unit = {
    Notifications.confirmation(locale.deleteConfirmation(postData.hash), Layout.topLeft) {
      NanoboardApi.delete(postData.hash).foreach { hashes ⇒
        controller.deleteSingle(postData)
        hashes.foreach { hash ⇒
          controller.deleteSingle(NanoboardMessageData(None, None, hash, "", 0))
        }
      }
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/PostExternalImage.scala version [35f715aa7f].





























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.karasiq.nanoboard.frontend.components.post

import scala.language.postfixOps

import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._

import com.karasiq.nanoboard.frontend.Icons

private[components] object PostExternalImage {
  def apply(url: String): PostExternalImage = {
    new PostExternalImage(url)
  }
}

private[components] final class PostExternalImage(url: String) extends BootstrapHtmlComponent {
  val opened = Var(false)
  val expanded = Var(false)

  private val imageStyleMod = Rx {
    val modifier: Modifier = if (expanded()) {
      Seq[Modifier](maxWidth := 100.pct, maxHeight := 100.pct)
    } else {
      Seq[Modifier](maxWidth := 200.px, maxHeight := 200.px)
    }
    modifier
  }

  private val image = Rx[Frag] {
    if (opened()) {
      img(display.block, src := url, imageStyleMod.auto, onclick := Callback.onClick(_ ⇒ expanded() = !expanded.now))
    } else {
      ""
    }
  }

  override def renderTag(md: Modifier*) = {
    span(
      a(href := url, fontWeight.bold, Icons.image, url, onclick := Callback.onClick(_ ⇒ opened() = !opened.now)),
      image,
      md
    )
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/PostExternalVideo.scala version [c2e3801b12].



































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.karasiq.nanoboard.frontend.components.post

import rx._
import scalatags.JsDom.all._

import com.karasiq.bootstrap.Bootstrap.default._
import com.karasiq.nanoboard.frontend.Icons
import com.karasiq.videojs.{VideoJSBuilder, VideoSource}

private[components] object PostExternalVideo {
  def defaultType = "webm"

  def apply(url: String, sources: VideoSource*): PostExternalVideo = {
    new PostExternalVideo(url, sources)
  }

  def youtube(url: String): PostExternalVideo = {
    new PostExternalVideo(url, Seq(VideoSource("video/youtube", url)), Seq("youtube"))
  }
}

private[components] final class PostExternalVideo(url: String, sources: Seq[VideoSource], techOrder: Seq[String] = Nil) extends BootstrapHtmlComponent {
  val expanded = Var(false)

  private val videoPlayer = Rx[Frag] {
    if (expanded()) {
      VideoJSBuilder()
        .techOrder(techOrder:_*)
        .sources(sources:_*)
        .dimensions(640, 360)
        .fluid(true)
        .autoplay(true)
        .loop(true)
        .controls(true)
        .options("iv_load_policy" → 1)
        .build()
    } else {
      ""
    }
  }

  override def renderTag(md: Modifier*) = {
    span(
      a(href := url, fontWeight.bold, Icons.video, url, onclick := Callback.onClick(_ ⇒ expanded() = !expanded.now)),
      videoPlayer,
      md
    )
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/PostFractalMusic.scala version [dcb7db3f15].









































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.karasiq.nanoboard.frontend.components.post

import scala.scalajs.js.URIUtils

import rx._
import scalatags.JsDom.all._

import com.karasiq.bootstrap.Bootstrap.default._
import com.karasiq.nanoboard.frontend.Icons

private[components] object PostFractalMusic {
  def apply(formula: String): PostFractalMusic = {
    new PostFractalMusic(formula)
  }
}

private[components] final class PostFractalMusic(formula: String) extends BootstrapHtmlComponent {
  val opened = Var(false)
  val url = s"/fractal_music/${URIUtils.encodeURIComponent(formula)}"

  private val player = Rx[Frag] {
    if (opened()) {
      audio(attr("controls") := "controls", attr("autoplay") := true, attr("loop") := true, display.block, `type` := "audio/wav", src := url)
    } else {
      ""
    }
  }

  override def renderTag(md: Modifier*) = {
    span(
      a(href := url, fontWeight.bold, Icons.music, formula, onclick := Callback.onClick(_ ⇒ opened() = !opened.now)),
      player,
      md
    )
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/PostInlineFile.scala version [b57359b780].

















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.karasiq.nanoboard.frontend.components.post

import scalatags.JsDom.all._

import com.karasiq.bootstrap.Bootstrap.default._
import com.karasiq.nanoboard.frontend.{Icons, NanoboardController}
import com.karasiq.nanoboard.frontend.utils.Blobs

private[components] object PostInlineFile {
  def apply(fileName: String, base64: String, fileType: String)(implicit controller: NanoboardController): PostInlineFile = {
    new PostInlineFile(fileName, base64, fileType)
  }
}

private[components] final class PostInlineFile(val fileName: String, val base64: String, val fileType: String)
                                              (implicit controller: NanoboardController) extends BootstrapHtmlComponent {
  val file = Blobs.fromBase64(base64, fileType)

  override def renderTag(md: Modifier*): TagT = {
    a(fontWeight.bold, href := "#", Icons.file, fileName, onclick := Callback.onClick { _ ⇒
      Blobs.saveBlob(file, fileName)
    })
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/PostInlineImage.scala version [b09bca03df].









































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.karasiq.nanoboard.frontend.components.post

import rx._
import scalatags.JsDom.all._

import com.karasiq.bootstrap.Bootstrap.default._
import com.karasiq.nanoboard.frontend.NanoboardController
import com.karasiq.nanoboard.frontend.utils.Blobs

private[components] object PostInlineImage {
  def defaultType = "jpeg"

  def apply(base64: String, imageType: String = defaultType)(implicit controller: NanoboardController): PostInlineImage = {
    new PostInlineImage(base64, imageType)
  }
}

private[components] final class PostInlineImage(val base64: String, val imageType: String)(implicit controller: NanoboardController)
  extends BootstrapHtmlComponent {

  val expanded = Var(false)

  private val styleMod = Rx {
    val modifier: Modifier = if (expanded()) {
      Seq[Modifier](maxWidth := 100.pct, maxHeight := 100.pct)
    } else {
      Seq[Modifier](maxWidth := 200.px, maxHeight := 200.px)
    }
    modifier
  }

  override def renderTag(md: Modifier*) = {
    val blobUrl = Blobs.asUrl(Blobs.fromBase64(base64, s"image/$imageType")) // s"data:image/jpeg;base64,$base64"
    img(alt := controller.locale.embeddedImage, src := blobUrl, styleMod.auto, onclick := Callback.onClick(_ ⇒ expanded() = !expanded.now), md)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/PostLink.scala version [ff385b2e65].





















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.karasiq.nanoboard.frontend.components.post

import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue

import rx._
import rx.async._
import scalatags.JsDom.all._

import com.karasiq.bootstrap.Bootstrap.default._
import com.karasiq.nanoboard.frontend.NanoboardController
import com.karasiq.nanoboard.frontend.api.NanoboardApi
import com.karasiq.nanoboard.frontend.utils.Mouse

private[components] object PostLink {
  def apply(hash: String)(implicit controller: NanoboardController): PostLink = {
    new PostLink(hash)
  }
}

private[components] final class PostLink(hash: String)(implicit controller: NanoboardController) extends BootstrapHtmlComponent {
  lazy val post = NanoboardApi.post(hash).toRx(None)
    .map(_.map(data ⇒ div(Mouse.relative(xOffset = 12), zIndex := 1, NanoboardPost(showParent = false, showAnswers = true, data)).render))

  private val hover = Var(false)

  override def renderTag(md: Modifier*): TagT = {
    val updateHover: Modifier = Seq(
      onmouseover := { () ⇒
        hover() = true
      }, onmouseout := { () ⇒
        hover() = false
      }
    )

    span(
      position.relative,
      a(updateHover, href := s"#$hash", onclick := Callback.onClick(_ ⇒ controller.showPost(hash)), md),
      Rx[Frag](if (hover() && post().nonEmpty) post().get else "")
    )
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/PostRenderer.scala version [bf563f5909].



























































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package com.karasiq.nanoboard.frontend.components.post

import scala.scalajs.js

import scalatags.JsDom.all._

import com.karasiq.bootstrap.Bootstrap.default._
import com.karasiq.highlightjs.HighlightJS
import com.karasiq.markedjs.{Marked, MarkedOptions, MarkedRenderer}
import com.karasiq.nanoboard.frontend.NanoboardController
import com.karasiq.nanoboard.frontend.utils._
import com.karasiq.nanoboard.frontend.utils.PostDomValue._
import com.karasiq.videojs.VideoSource

private[components] object PostRenderer {
  def apply()(implicit controller: NanoboardController): PostRenderer = {
    new PostRenderer
  }

  // Returns HTML string
  def formatCode(source: String, language: Option[String]): String = {
    import scalatags.Text.all.{source ⇒ _, _}
    val result = language.fold(HighlightJS.highlightAuto(source))(HighlightJS.highlight(_, source))
    span(whiteSpace.`pre-wrap`, code(`class` := s"hljs ${result.language}", raw(result.value))).render
  }

  def renderMarkdown(source: String): Frag = {
    val renderer = MarkedRenderer(
      image = { (url: String, imageTitle: String, text: String) ⇒
        import scalatags.Text.all._
        a(href := url, title := text, target := "_blank", imageTitle).render
      },
      code = { (source: String, language: String) ⇒
        formatCode(source, if (js.isUndefined(language)) None else Some(language))
      },
      table = { (header: String, body: String) ⇒
        import scalatags.Text.all.{body ⇒ _, header ⇒ _, _}
        div(`class` := "table-responsive", table(`class` := "table", thead(raw(header)), tbody(raw(body)))).render
      }
    )

    val options = MarkedOptions(
      renderer = renderer,
      gfm = true,
      tables = true,
      breaks = true,
      pedantic = false,
      sanitize = true,
      smartLists = true,
      smartypants = true
    )

    span(whiteSpace.normal, raw(Marked(source, options)))
  }

  def asText(parsed: PostDomValue): String = parsed match {
    case PlainText(value) ⇒
      value

    case PostDomValues(values) ⇒
      values.map(asText).mkString

    case BBCode("plain", _, value) ⇒
      asText(value)

    case BBCode(name, parameters, value) ⇒
      s"[$name${if (parameters.isEmpty) "" else parameters.map(p ⇒ p._1 + "=\"" + p._2 + "\"").mkString(" ", " ", "")}]" + asText(value) + s"[/$name]"

    case ShortBBCode(name, value) ⇒
      s"[$name=$value]"
  }

  def strip(parsed: PostDomValue): String = parsed match {
    case PlainText(value) ⇒
      value

    case PostDomValues(values) ⇒
      values.map(strip).mkString

    case BBCode("md" | "img" | "xmg" | "file" | "g" | "sp" | "spoiler", _, _) ⇒
      ""

    case BBCode(_, _, value) ⇒
      strip(value)

    case ShortBBCode("svid" | "simg", url) ⇒
      url

    case _ ⇒
      ""
  }
}

private[components] final class PostRenderer(implicit controller: NanoboardController) {
  def render(parsed: PostDomValue): Frag = parsed match {
    case PlainText(value) ⇒
      Linkifier(value)

    case PostDomValues(values) ⇒
      values.map(render)

    case BBCode("md", _, value) ⇒
      PostRenderer.renderMarkdown(PostRenderer.asText(value))

    case BBCode("b", _, value) ⇒
      strong(render(value))

    case BBCode("i", _, value) ⇒
      em(render(value))

    case BBCode("u", _, value) ⇒
      u(render(value))

    case BBCode("s", _, value) ⇒
      s(render(value))

    case BBCode("g", _, value) ⇒
      span(controller.style.greenText, render(value))

    case BBCode("sp" | "spoiler", _, value) ⇒
      span(controller.style.spoiler, render(value))

    case ShortBBCode("img" | "xmg", base64) ⇒
      PostInlineImage(base64)

    case BBCode("img", parameters, value) ⇒
      PostInlineImage(PostRenderer.asText(value), parameters.getOrElse("type", PostInlineImage.defaultType))

    case ShortBBCode("simg", url) ⇒
      PostExternalImage(url)

    case BBCode("video", parameters, value) ⇒
      val url = PostRenderer.asText(value)
      PostExternalVideo(url, VideoSource(s"video/${parameters.getOrElse("type", PostExternalVideo.defaultType)}", url))

    case ShortBBCode("svid", url) ⇒
      PostExternalVideo(url, VideoSource(s"video/${PostExternalVideo.defaultType}", url))

    case ShortBBCode("fm", music) ⇒
      PostFractalMusic(music)

    case BBCode("code", parameters, source) ⇒
      span(raw(PostRenderer.formatCode(PostRenderer.asText(source), parameters.get("lang"))))

    case BBCode("file", parameters, value) ⇒
      PostInlineFile(parameters.getOrElse("name", controller.locale.file), PostRenderer.asText(value), parameters.getOrElse("type", ""))

    case BBCode("link", parameters, value) ⇒
      a(target := "_blank", href := parameters.getOrElse("url", PostRenderer.asText(value)), render(value))

    case BBCode("small", _, value) ⇒
      small(render(value))

    case BBCode("abbr", parameters, value) ⇒
      val abbr = tag("abbr")
      abbr(title := parameters.getOrElse("title", ""), render(value))

    case BBCode("mark", _, value) ⇒
      val mark = tag("mark")
      mark(render(value))

    case BBCode("quote", parameters, value) ⇒
      blockquote(
        if (parameters.contains("reverse")) "blockquote-reverse".addClass else (),
        p(render(value)),
        parameters.get("title").map(footer(_))
      )

    // Unknown
    case value ⇒
      PostRenderer.asText(value)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/actions/CaptchaDialog.scala version [0faa598008].















































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.karasiq.nanoboard.frontend.components.post.actions

import scala.concurrent.{Future, Promise}
import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.util.{Failure, Success}

import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._

import com.karasiq.bootstrap.Bootstrap.default._
import com.karasiq.nanoboard.api.{NanoboardCaptchaRequest, NanoboardMessageData}
import com.karasiq.nanoboard.frontend.NanoboardController
import com.karasiq.nanoboard.frontend.api.NanoboardApi
import com.karasiq.nanoboard.frontend.utils.{Blobs, CancelledException, Notifications}
import com.karasiq.nanoboard.frontend.utils.Notifications.Layout

private[components] object CaptchaDialog {
  def apply()(implicit controller: NanoboardController): CaptchaDialog = {
    new CaptchaDialog()
  }
}

/**
  * Captcha dialog
  */
private[components] final class CaptchaDialog(implicit controller: NanoboardController) {
  import controller.locale

  val answer = Var("")

  val ready = Rx {
    answer().nonEmpty
  }

  def verify(hash: String): Future[NanoboardMessageData] = {
    for {
      request ← NanoboardApi.requestVerification(hash)
      result ← solveCaptcha(request)
    } yield result
  }

  def solveCaptcha(request: NanoboardCaptchaRequest): Future[NanoboardMessageData] = {
    val promise = Promise[NanoboardMessageData]
    val modal = Modal(locale.verify)
      .withBody(Form(
        img(display.block, height := 60.px, src := Blobs.asUrl(Blobs.fromBytes(request.captcha.image, "image/png"))),
        FormInput.text((), answer.reactiveInput),
        onsubmit := Callback.onSubmit(_ ⇒ ())
      ))
      .withButtons(
        Modal.closeButton(locale.cancel)(onclick := Callback.onClick { _ ⇒
          promise.failure(CancelledException)
        }),
        Button(ButtonStyle.success)(locale.submit, Modal.dismiss, ready.reactiveShow, onclick := Callback.onClick { _ ⇒
          NanoboardApi.verifyPost(request, answer.now) onComplete {
            case Success(data) ⇒
              promise.success(data)

            case Failure(exc) ⇒
              Notifications.error(exc)(locale.verificationError, Layout.topRight)
              promise.completeWith(solveCaptcha(request)) // Retry
          }
        })
      )
    modal.show(backdrop = false)
    promise.future
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/actions/ImageAttachDialog.scala version [e7dc3e1e38].































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package com.karasiq.nanoboard.frontend.components.post.actions

import scala.concurrent.{Future, Promise}
import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.util.{Failure, Success, Try}

import org.scalajs.dom.raw.File
import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._
import scalatags.JsDom.all._

import com.karasiq.nanoboard.frontend.NanoboardController
import com.karasiq.nanoboard.frontend.api.NanoboardApi
import com.karasiq.nanoboard.frontend.components.post.PostInlineImage
import com.karasiq.nanoboard.frontend.utils.{Blobs, CancelledException, Images, Notifications}
import com.karasiq.nanoboard.frontend.utils.Notifications.Layout

case class ImageData(base64: String, format: String)

private[components] object ImageAttachDialog {
  def apply()(implicit controller: NanoboardController): ImageAttachDialog = {
    new ImageAttachDialog()
  }
}

private[components] final class ImageAttachDialog(implicit controller: NanoboardController) {
  import controller.locale

  val scale = Var("50")
  val size = Var("500")
  val quality = Var("50")
  val sharpness = Var("50")
  val files = Var[Seq[File]](Nil)
  val useServer = Var(false)

  val formatSelect = FormInput.select(locale.imageFormat, Rx {
    val options = if (!useServer() && Images.isWebpSupported) {
      Seq("jpeg", "webp", "png")
    } else {
      Seq("jpeg", "png")
    }
    options.map(str ⇒ FormSelectOption(str, str))
  })

  lazy val format = formatSelect.selected.map(_.head)

  lazy val ready = Rx {
    def isValidPct(value: Rx[String]): Boolean = Try(value().toInt).filter((1 to 100).contains).isSuccess
    files().nonEmpty && ((useServer() && Try(size().toInt).filter(_ > 0).isSuccess) || isValidPct(scale)) &&
      isValidPct(quality) && (useServer() || isValidPct(sharpness))
  }

  def generate(): Future[ImageData] = {
    val promise = Promise[ImageData]
    val preview = Var[Option[PostInlineImage]](None)
    val modal = Modal(locale.insertImage)
      .withBody(Form(
        formatSelect,
        Rx {
          if (useServer()) FormInput.number(locale.imageSize, name := "size", min := 1, size.reactiveInput, placeholder := 500)
          else FormInput.number(locale.imageScale, name := "scale", min := 1, max := 100, scale.reactiveInput, placeholder := 50)
        },
        FormInput.number(locale.imageQuality, name := "quality", min := 1, max := 100, quality.reactiveInput, placeholder := 50),
        Rx[Frag] {
          if (useServer()) "" else FormInput.number(locale.imageSharpness, name := "sharpness", min := 1, max := 100, sharpness.reactiveInput, placeholder := 50)
        },
        FormInput.file(locale.dataContainer, name := "image", files.reactiveInputRead),
        FormInput.checkbox(locale.useServerRendering, useServer.reactiveInput),
        div(preview.map(_.map(_.base64.length).fold[Frag]("")(length ⇒ s"$length ${locale.bytes}"))),
        div(preview.map(_.fold[Frag]("")(img ⇒ img))),
        onsubmit := Callback.onSubmit(_ ⇒ ())
      ))
      .withButtons(
        Modal.closeButton(locale.cancel)(onclick := Callback.onClick { _ ⇒
          promise.failure(CancelledException)
        }),
        Button(ButtonStyle.info)(locale.preview, ready.reactiveShow)(onclick := Callback.onClick { _ ⇒
          createBase64Image().onComplete {
            case Success(ImageData(base64, format)) ⇒
              preview() = Some(PostInlineImage(base64, format))

            case Failure(exc) ⇒
              Notifications.error(exc)(locale.attachmentGenerationError, Layout.topRight)
              preview() = None
          }
        }),
        Button(ButtonStyle.success)(locale.submit, Modal.dismiss, ready.reactiveShow, onclick := Callback.onClick { _ ⇒
          promise.completeWith(createBase64Image())
        })
      )
    modal.show(backdrop = false)
    promise.future
  }

  private def createBase64Image(): Future[ImageData] = files.now.headOption match {
    case Some(file) if file.`type` ==  "image/svg+xml" ⇒
      Blobs.asBase64(file).map(ImageData(_, "svg+xml"))

    case Some(file) if useServer.now ⇒
      NanoboardApi.generateAttachment(format.now, size.now.toInt, quality.now.toInt, file).map(ImageData(_, format.now))

    case Some(file) ⇒
      Images.compress(file, s"image/${format.now}", scale.now.toInt, quality.now.toInt, sharpness.now.toInt).map(ImageData(_, format.now))

    case None ⇒
      Future.failed(new NoSuchElementException("No file selected"))
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/actions/PendingButton.scala version [509ca5f560].











































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.karasiq.nanoboard.frontend.components.post.actions

import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue

import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._

import com.karasiq.nanoboard.api.NanoboardMessageData
import com.karasiq.nanoboard.frontend.{Icons, NanoboardController}
import com.karasiq.nanoboard.frontend.api.NanoboardApi

private[post] object PendingButton {
  def apply(post: NanoboardMessageData)(implicit controller: NanoboardController) = {
    new PendingButton(post)
  }
}

private[post] final class PendingButton(post: NanoboardMessageData)(implicit controller: NanoboardController) extends BootstrapComponent {
  import controller.{locale, style}

  override def render(md: Modifier*): Modifier = {
    controller.isPending(post.hash).map { pending ⇒
      if (!pending) a(style.postLink, href := "#", Icons.enqueue, locale.enqueue, onclick := Callback.onClick { a ⇒
        NanoboardApi.markAsPending(post.hash).foreach { _ ⇒
          controller.addPending(post)
        }
      }) else a(style.postLink, href := "#", Icons.dequeue, locale.dequeue, onclick := Callback.onClick { a ⇒
        NanoboardApi.markAsNotPending(post.hash).foreach { _ ⇒
          controller.deletePending(post)
        }
      })
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/actions/ReplyField.scala version [c27464fc4b].

























































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package com.karasiq.nanoboard.frontend.components.post.actions

import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.util.{Failure, Success}

import org.scalajs.dom.Element
import org.scalajs.dom.html.TextArea
import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._

import com.karasiq.nanoboard.api.NanoboardMessageData
import com.karasiq.nanoboard.frontend.{Icons, NanoboardController}
import com.karasiq.nanoboard.frontend.api.NanoboardApi
import com.karasiq.nanoboard.frontend.components.post.NanoboardPost
import com.karasiq.nanoboard.frontend.styles.CommonStyles
import com.karasiq.nanoboard.frontend.utils.{Blobs, CancelledException, Notifications}
import com.karasiq.nanoboard.frontend.utils.Notifications.Layout
import com.karasiq.taboverridejs.TabOverride

private[post] object ReplyField {
  def apply(post: NanoboardMessageData)(implicit controller: NanoboardController): ReplyField = {
    new ReplyField(post)
  }

  def tabOverride: Modifier = new Modifier {
    override def applyTo(t: Element): Unit = {
      TabOverride.set(t.asInstanceOf[TextArea])
    }
  }
}

private[post] final class ReplyField(post: NanoboardMessageData)(implicit controller: NanoboardController) extends BootstrapHtmlComponent {
  import controller.{locale, style}

  val expanded = Var(false)
  val replyText = Var("")
  val lengthIsValid = replyText.map(text ⇒ (1 to 65535).contains(text.length))

  override def renderTag(md: Modifier*) = {
    val field = Form(
      FormInput.textArea((), style.input, placeholder := locale.writeYourMessage, rows := 5, replyText.reactiveInput, "has-errors".classIf(lengthIsValid.map(!_)), ReplyField.tabOverride)
    )

    val imageLink = Button(ButtonStyle.primary)(Icons.image, locale.insertImage, onclick := Callback.onClick { _ ⇒
      ImageAttachDialog().generate().onComplete {
        case Success(ImageData(base64, format)) ⇒
          val data = if (format == "svg+xml") "[img type=\"svg+xml\"]" + base64 + "[/img]" else s"[img=$base64]"
          replyText() = s"${replyText.now}$data"

        case Failure(CancelledException) ⇒
          // Pass

        case Failure(exc) ⇒
          Notifications.error(exc)(locale.attachmentGenerationError, Layout.topRight)
      }
    })

    val fileLink = Button(ButtonStyle.info)(Icons.file, locale.file, onclick := Callback.onClick { _ ⇒
      val field = input(`type` := "file", onchange := Callback.onInput { field ⇒
        val file = field.files.head
        Blobs.asBase64(file).foreach { base64 ⇒
          replyText() = s"${replyText.now}${if (replyText.now.nonEmpty) "\n" else ""}[file name=${'"' + file.name + '"'} type=${'"' + file.`type` + '"'}]$base64[/file]"
        }
      }).render
      field.click()
    })

    val submitButton = Button(ButtonStyle.success)(/* "disabled".classIf(lengthIsValid.map(!_)),*/ Icons.submit, locale.submit, onclick := Callback.onClick { _ ⇒
      if (/* lengthIsValid.now */ replyText.now.nonEmpty) {
        NanoboardApi.addReply(post.hash, replyText.now).onComplete {
          case Success(newPost) ⇒
            expanded() = false
            replyText() = ""
            controller.addPost(newPost)

          case Failure(exc) ⇒
            Notifications.error(exc)(locale.postingError, Layout.topRight)
        }
      }
    })

    val postLength = Rx {
      span(float.right, fontStyle.italic, if (!lengthIsValid()) color.red else (), s"${replyText().length} ${locale.bytes}")
    }

    span(
      // Reply link
      a(style.postLink, href := "#", Icons.reply, locale.reply, onclick := Callback.onClick { _ ⇒
        expanded() = !expanded.now
      }),

      // Input field
      div(field,
        ButtonGroup(ButtonGroupSize.small, imageLink, fileLink, submitButton),
        postLength,
        expanded.reactiveShow
      ),

      // Preview
      div(marginTop := 20.px, style.post, Rx(span(style.postInner, CommonStyles.flatScroll, NanoboardPost.render(replyText()))), Rx(expanded() && replyText().nonEmpty).reactiveShow),

      md
    )
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/components/post/actions/VerificationButton.scala version [083a68319e].



























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.karasiq.nanoboard.frontend.components.post.actions

import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.util.{Failure, Success}

import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._
import scalatags.JsDom.all._

import com.karasiq.nanoboard.api.NanoboardMessageData
import com.karasiq.nanoboard.frontend.{Icons, NanoboardController}
import com.karasiq.nanoboard.frontend.utils.{CancelledException, Notifications}
import com.karasiq.nanoboard.frontend.utils.Notifications.Layout

private[post] object VerificationButton {
  def apply(post: NanoboardMessageData)(implicit controller: NanoboardController) = {
    new VerificationButton(post)
  }
}

private[post] final class VerificationButton(post: NanoboardMessageData)(implicit controller: NanoboardController) extends BootstrapHtmlComponent {
  import controller.{locale, style}
  val hidden = Var(post.isSigned || post.isCategory)

  override def renderTag(md: Modifier*): TagT = {
    a(style.postLink, href := "#", Icons.verify, locale.verify, Rx(if (hidden()) display.none else display.inline).auto, onclick := Callback.onClick { _ ⇒
      hidden() = true
      CaptchaDialog().verify(post.hash).onComplete {
        case Success(verified) ⇒
          Notifications.success(locale.verificationSuccess(verified.hash), Layout.topRight)
          controller.addPending(verified)

        case Failure(CancelledException) ⇒
          hidden() = false

        case Failure(exc) ⇒
          hidden() = false
          Notifications.error(exc)(locale.verificationError, Layout.topRight)
      }
    })
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/locales/BoardLocale.scala version [7216e6386f].































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.karasiq.nanoboard.frontend.locales

import com.karasiq.nanoboard.api.NanoboardContainer

trait BoardLocale {
  def nanoboard: String
  def settings: String
  def containerGeneration: String
  def recentPosts: String
  def recentPostsFrom(post: Int): String
  def categories: String
  def places: String
  def delete: String
  def deleteConfirmation(hash: String): String
  def enqueue: String
  def dequeue: String
  def reply: String
  def insertImage: String
  def submit: String
  def cancel: String
  def pendingPosts: String
  def randomPosts: String
  def imageScale: String
  def imageSize: String
  def imageQuality: String
  def imageFormat: String
  def imageSharpness: String
  def useServerRendering: String
  def preview: String
  def dataContainer: String
  def generateContainer: String
  def fromTo(from: Int, to: Int): String
  def embeddedImage: String
  def writeYourMessage: String
  def bytes: String
  def style: String
  def preferences: String
  def control: String
  def offset: String
  def count: String
  def batchDelete: String
  def batchDeleteConfirmation(count: Int): String
  def batchDeleteSuccess(count: Int): String
  def clearDeleted: String
  def clearDeletedConfirmation: String
  def clearDeletedSuccess(count: Int): String
  def containers: String
  def container(c: NanoboardContainer): String
  def file: String
  def source: String
  def verify: String
  def verificationError: String
  def verificationSuccess(hash: String): String
  def clearDeletedError: String
  def postingError: String
  def updateError: String
  def containerGenerationError: String
  def attachmentGenerationError: String
  def settingsUpdateError: String
  def batchDeleteError: String
  def webSocketError: String
}

object BoardLocale {
  def browserLanguage: String = {
    import org.scalajs.dom.window.navigator
    navigator.language
  }

  def fromBrowserLanguage(): BoardLocale = {
    browserLanguage.toLowerCase match {
      case "ru-ru" | "ru" ⇒
        Russian

      case _ ⇒
        English
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/locales/English.scala version [e675ee496c].





























































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.karasiq.nanoboard.frontend.locales

import com.karasiq.nanoboard.api.NanoboardContainer

object English extends BoardLocale {
  def nanoboard = "Nanoboard"
  def generateContainer = "Generate container"
  def cancel = "Cancel"
  def dataContainer = "Source file"
  def dequeue = "Dequeue"
  def insertImage = "Picture"
  def recentPostsFrom(post: Int) = s"Recent posts (from $post)"
  def categories = "Categories"
  def pendingPosts = "Pending posts"
  def containerGeneration = "Container generation"
  def delete = "Delete"
  def imageFormat = "Image format"
  def imageSharpness = "Image sharpness"
  def enqueue = "Enqueue"
  def recentPosts = "Recent posts"
  def reply = "Reply"
  def settings = "Settings"
  def places = "Places"
  def imageScale = "Image scale (%)"
  def imageSize = "Image size (pixels)"
  def useServerRendering = "Use server rendering"
  def preview = "Preview"
  def submit = "Submit"
  def randomPosts = "Random posts"
  def imageQuality = "Image quality"
  def deleteConfirmation(hash: String) = s"Are you sure you want to permanently delete post #$hash?"
  def writeYourMessage = "Write your message"
  def bytes = "bytes"
  def style = "Style"
  def fromTo(from: Int, to: Int) = s"From $from to $to"
  def embeddedImage = "Embedded image"
  def preferences = "Preferences"
  def control = "Control"
  def offset = "Offset"
  def count = "Count"
  def batchDelete = "Batch delete"
  def batchDeleteConfirmation(count: Int) = s"Are you sure you want to permanently delete $count posts?"
  def batchDeleteSuccess(count: Int) = s"$count posts successfully removed"
  def clearDeleted = "Clear deleted posts"
  def clearDeletedConfirmation = "Clear deleted posts cache?"
  def clearDeletedSuccess(count: Int) = s"$count deleted posts evicted"
  def containers = "Containers"
  def container(c: NanoboardContainer) = s"№${c.id}, ${c.posts} posts"
  def file = "File"
  def source = "Source"
  def verify = "Verify"
  def verificationError = "Verification error"
  def verificationSuccess(hash: String) = s"Post verified: #$hash"
  def webSocketError = "WebSocket error"
  def clearDeletedError = "Clearing deleted posts failure"
  def containerGenerationError = "Container generation failure"
  def attachmentGenerationError = "Attachment generation error"
  def batchDeleteError = "Batch deletion error"
  def settingsUpdateError = "Settings update error"
  def postingError = "Posting error"
  def updateError = "Update error"
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/locales/Russian.scala version [397350a577].





























































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.karasiq.nanoboard.frontend.locales

import com.karasiq.nanoboard.api.NanoboardContainer

object Russian extends BoardLocale {
  def nanoboard = "Наноборда"
  def generateContainer = "Создать контейнер"
  def cancel = "Отмена"
  def dataContainer = "Исходный файл"
  def dequeue = "Из очереди"
  def insertImage = "Изображение"
  def recentPostsFrom(post: Int) = s"Недавние сообщения, начиная с $post"
  def categories = "Категории"
  def pendingPosts = "Сообщения, ожидающие отправки"
  def containerGeneration = "Генерация контейнера"
  def delete = "Удалить"
  def imageFormat = "Формат изображения"
  def enqueue = "В очередь"
  def recentPosts = "Недавние сообщения"
  def reply = "Ответить"
  def settings = "Настройки"
  def places = "Треды с контейнерами"
  def imageScale = "Размер изображения в процентах"
  def imageSize = "Размер изображения в пикселях"
  def submit = "Отправить"
  def randomPosts = "Случайные сообщения"
  def imageQuality = "Качество изображения"
  def imageSharpness = "Резкость изображения"
  def useServerRendering = "Использовать серверный рендеринг"
  def preview = "Предпросмотр"
  def deleteConfirmation(hash: String) = s"Вы уверены, что хотите навсегда удалить сообщение #$hash?"
  def writeYourMessage = "Введите сообщение"
  def bytes = "байт"
  def style = "Стиль оформления"
  def fromTo(from: Int, to: Int) = s"С $from по $to"
  def embeddedImage = "Встроенное изображение"
  def preferences = "Опции"
  def control = "Управление"
  def offset = "Начиная с"
  def count = "Количество"
  def source = "Текст"
  def batchDelete = "Массовое удаление"
  def batchDeleteConfirmation(count: Int) = s"Вы уверены, что хотите навсегда удалить $count сообщений?"
  def batchDeleteSuccess(count: Int) = s"$count сообщений успешно удалено"
  def clearDeleted = "Очистка удалённых сообщений"
  def clearDeletedConfirmation = "Очистить кэш удалённых сообщений?"
  def clearDeletedSuccess(count: Int) = s"$count удалённых сообщений очищенно"
  def containers = "Принятые контейнеры"
  def container(c: NanoboardContainer) = s"№${c.id}, ${c.posts} сообщений"
  def file = "Файл"
  def verify = "Подтвердить"
  def verificationError = "Ошибка подтверждения"
  def verificationSuccess(hash: String) = s"Сообщение подтверждено: #$hash"
  def webSocketError = "Ошибка WebSocket"
  def clearDeletedError = "Ошибка очистки кэша удалённых сообщений"
  def postingError = "Ошибка отправки сообщения"
  def updateError = "Ошибка обновления"
  def containerGenerationError = "Ошибка создания контейнера"
  def attachmentGenerationError = "Ошибка вставки изображения"
  def batchDeleteError = "Ошибка массового удаления"
  def settingsUpdateError = "Ошибка применения настроек"
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/model/ThreadModel.scala version [e604b30224].







































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package com.karasiq.nanoboard.frontend.model

import scala.concurrent.Future
import scala.language.postfixOps
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.util.{Failure, Success}

import rx._

import com.karasiq.bootstrap.Bootstrap.default._
import scalaTags.all._

import com.karasiq.nanoboard.api.NanoboardMessageData
import com.karasiq.nanoboard.frontend.{NanoboardContext, NanoboardController}
import com.karasiq.nanoboard.frontend.api.NanoboardApi
import com.karasiq.nanoboard.frontend.utils.Notifications
import com.karasiq.nanoboard.frontend.utils.Notifications.Layout

private[frontend] object ThreadModel {
  def apply(context: Var[NanoboardContext], postsPerPage: Int)(implicit controller: NanoboardController) = {
    new ThreadModel(context, postsPerPage)
  }
}

private[frontend] final class ThreadModel(val context: Var[NanoboardContext], postsPerPage: Int)(implicit controller: NanoboardController) {
  import controller.locale

  val addedPosts = Var(Set.empty[String])
  val deletedPosts = Var(Set.empty[String])
  val categories = Var(Vector.empty[NanoboardMessageData])
  val posts = Var(Vector.empty[NanoboardMessageData])

  def addPost(post: NanoboardMessageData): Unit = {
    if (!addedPosts.now.contains(post.hash) && !posts.now.exists(_.hash == post.hash)) {
      posts() = context.now match {
        case NanoboardContext.Recent(0) ⇒
          if (posts.now.length == postsPerPage) {
            post +: posts.now.dropRight(1)
          } else {
            post +: posts.now
          }

        case NanoboardContext.Pending(_) if posts.now.length < postsPerPage ⇒
          posts.now :+ post

        case NanoboardContext.Thread(hash, 0) if post.parent.contains(hash) ⇒
          val (opPost, answers) = posts.now.partition(_.hash == hash)
          if (answers.length >= postsPerPage) {
            opPost ++ Some(post) ++ answers.dropRight(1)
          } else {
            opPost ++ Some(post) ++ answers
          }

        case NanoboardContext.Thread(post.hash, _) ⇒
          val (_, answers) = posts.now.partition(_.hash == post.hash)
          post +: answers

        case _ ⇒
          posts.now
      }
      updateAnswersCount(+1, post, posts)
      addedPosts() = addedPosts.now + post.hash
      deletedPosts() = deletedPosts.now - post.hash
      updateAnswersCount(+1, post, categories)
    }
  }

  def deleteTree(post: NanoboardMessageData): Unit = {
    deleteBy(post, p ⇒ p.hash == post.hash || p.parent.contains(post.hash))
  }

  def deleteSingle(post: NanoboardMessageData): Unit = {
    deleteBy(post, _.hash == post.hash)
  }

  def updatePosts(): Unit = {
    val future = context.now match {
      case NanoboardContext.Categories ⇒
        Future.successful(categories.now)

      case NanoboardContext.Thread(hash, offset) ⇒
        NanoboardApi.thread(hash, offset, postsPerPage)

      case NanoboardContext.Recent(offset) ⇒
        NanoboardApi.recent(offset, postsPerPage)

      case NanoboardContext.Pending(offset) ⇒
        NanoboardApi.pending(offset, postsPerPage)
    }

    future.onComplete {
      case Success(posts) ⇒
        this.addedPosts() = Set.empty
        this.deletedPosts() = Set.empty
        this.posts() = posts

      case Failure(exc) ⇒
        Notifications.error(exc)(locale.updateError, Layout.topRight)
    }
  }

  def updateCategories(): Unit = {
    NanoboardApi.categories().onComplete {
      case Success(categories) ⇒
        this.categories() = categories

      case Failure(exc) ⇒
        Notifications.error(exc)(locale.updateError, Layout.topRight)
    }
  }

  private[this] def updateAnswersCount(i: Int, post: NanoboardMessageData, posts: Var[Vector[NanoboardMessageData]]): Unit = {
    posts() = posts.now.collect {
      case msg @ NanoboardMessageData(_, _, hash, _, answers, _, _) if post.parent.contains(hash) ⇒
        msg.copy(answers = answers + i)

      case msg ⇒
        msg
    }
  }

  private[this] def deleteBy(post: NanoboardMessageData, f: (NanoboardMessageData) ⇒ Boolean): Unit = {
    if (!deletedPosts.now.contains(post.hash)) {
      context.now match {
        case NanoboardContext.Thread(post.hash, _) ⇒
          context() = post.parent.fold[NanoboardContext](NanoboardContext.Categories)(NanoboardContext.Thread(_))
          deletedPosts() = deletedPosts.now + post.hash

        case NanoboardContext.Thread(hash, _) ⇒
          val (opPost, answers) = posts.now.partition(_.hash == hash)
          val filtered = answers.filterNot(f)
          posts() = opPost.map(p ⇒ p.copy(answers = p.answers - 1)) ++ filtered
          deletedPosts() = deletedPosts.now ++ answers.diff(filtered).map(_.hash)

        case _ ⇒
          val current = posts.now
          val filtered = current.filterNot(f)
          posts() = filtered
          deletedPosts() = deletedPosts.now ++ current.diff(filtered).map(_.hash)
      }

      updateAnswersCount(-1, post, posts)
      categories() = categories.now.filterNot(f)
      updateAnswersCount(-1, post, categories)
      addedPosts() = addedPosts.now - post.hash
    }
  }

  // Initialization
  private[this] def initialize(): Unit = {
    context.foreach(_ ⇒ updatePosts())
    categories.foreach { categories ⇒
      if (context.now == NanoboardContext.Categories && posts.now != categories) {
        addedPosts() = Set.empty
        deletedPosts() = Set.empty
        posts() = categories
      }
    }
    updateCategories()
  }

  initialize()
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/styles/BoardStyle.scala version [9407a3a16d].















































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.karasiq.nanoboard.frontend.styles

import scala.scalajs.js.UndefOr

import org.scalajs.dom._
import rx._
import scalatags.JsDom.tags2
import scalatags.JsDom.all._
import scalatags.stylesheet.{StyleSheet, _}

import com.karasiq.bootstrap.Bootstrap.default._

trait BoardStyle extends StyleSheet {
  override final def customSheetName: Option[String] = Some("nanoboard")

  def body: Cls
  def post: Cls
  def postInner: Cls
  def postId: Cls
  def postLink: Cls
  def input: Cls
  def submit: Cls

  def spoiler: Cls
  def greenText: Cls
}

object BoardStyle {
  lazy val styles: Seq[BoardStyle] = {
    Vector(Makaba, Futaba, Burichan, Muon, Neutron, Gurochan)
  }

  def fromString(style: String): BoardStyle = {
    styles.find(_.toString == style).getOrElse(Makaba)
  }

  def selector: Selector = {
    new Selector()
  }

  final class Selector extends BootstrapHtmlComponent {
    val style: Var[BoardStyle] = Var {
      val styleName: UndefOr[String] = window.localStorage.getItem("nanoboard-style")
      styleName.map(fromString).getOrElse(Makaba)
    }

    style.foreach { style ⇒
      window.localStorage.setItem("nanoboard-style", style.toString)
    }

    override def renderTag(md: Modifier*) = {
      tags2.style(style.map(_.styleSheetText), CommonStyles.styleSheetText, md)
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/styles/Burichan.scala version [2feaf19197].







































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.karasiq.nanoboard.frontend.styles

import scalatags.Text.all._

object Burichan extends BoardStyle {
  initStyleSheet()

  val body = cls(
    color := "#000000",
    backgroundColor := "#EEF2FF"
  )

  val post = cls(
    minWidth := 40.pct,
    maxWidth := 100.pct,
    border := "solid 1px #CCCCCC",
    borderRadius := 2.px,
    display.`inline-block`,
    background := "#D6DAF0",
    margin := 0.25.em,
    clear.both,
    padding := "0.5em 1.5em"
  )

  val postInner = cls(
    marginBottom := 0.5.em,
    fontSize := 0.9.em,
    fontFamily := "Verdana,sans-serif"
  )

  val postId = cls(
    color := "#789922",
    marginRight := 0.5.em
  )

  val postLink = cls(
    color := "#34345C",
    &.hover(
      color.red
    ),
    cursor.pointer,
    marginRight := 0.5.em
  )

  val input = cls()

  val submit = cls()

  val greenText = cls(
    color.green,
    fontSize := 90.pct,
    lineHeight := 2.em
  )

  val spoiler = cls(
    textDecoration.none,
    color := "#9988EE",
    background := "#9988EE",
    &.hover(
      color := "#34345C"
    )
  )

  override def toString: String = {
    "Burichan"
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/styles/CommonStyles.scala version [3e1bb9106d].

































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.karasiq.nanoboard.frontend.styles

import scalatags.Text.all._
import scalatags.stylesheet._

object CommonStyles extends StyleSheet {
  initStyleSheet()

  val flatScroll = cls(
    overflowX.hidden,
    overflowY.auto,
    whiteSpace.`pre-wrap`,
    wordWrap.`break-word`,
    new Selector(Seq("::-webkit-scrollbar")).apply(display.none)
  )
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/styles/Futaba.scala version [2ceb8b2523].









































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.karasiq.nanoboard.frontend.styles

import scalatags.Text.all._

object Futaba extends BoardStyle {
  initStyleSheet()

  val body = cls(
    color := "#800000",
    backgroundColor := "#FFFFEE"
  )

  val post = cls(
    minWidth := 40.pct,
    maxWidth := 100.pct,
    border := "solid 1px #F0D0B6",
    borderRadius := 2.px,
    display.`inline-block`,
    background := "#F0E0D6",
    color := "#800000",
    margin := 0.25.em,
    clear.both,
    padding := "0.5em 1.5em"
  )

  val postInner = cls(
    marginBottom := 0.5.em,
    fontSize := 0.9.em,
    fontFamily := "Verdana,sans-serif"
  )

  val postId = cls(
    color := "#789922",
    marginRight := 0.5.em
  )

  val postLink = cls(
    color := "#0000EE",
    &.hover(
      color.red
    ),
    cursor.pointer,
    marginRight := 0.5.em
  )

  val input = cls()

  val submit = cls()

  val greenText = cls(
    color.green,
    fontSize := 90.pct,
    lineHeight := 2.em
  )

  val spoiler = cls(
    textDecoration.none,
    color := "#F0D0B6",
    background := "#F0D0B6",
    &.hover(
      color := "#0000EE"
    )
  )

  override def toString: String = {
    "Futaba"
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/styles/Gurochan.scala version [ec6d10bb4c].







































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.karasiq.nanoboard.frontend.styles

import scalatags.Text.all._

object Gurochan extends BoardStyle {
  initStyleSheet()
  
  val body = cls(
    color := "#000000",
    backgroundColor := "#EDDAD2"
  )

  val post = cls(
    minWidth := 40.pct,
    maxWidth := 100.pct,
    border := "1px solid #CA927B",
    borderRadius := 2.px,
    display.`inline-block`,
    background := "#D9AF9E",
    margin := 0.25.em,
    clear.both,
    padding := "0.5em 1.5em"
  )

  val postInner = cls(
    marginBottom := 0.5.em,
    fontSize := 0.9.em,
    fontFamily := "Trebuchet MS, Verdana, sans-serif"
  )

  val postId = cls(
    color := "#789922",
    marginRight := 0.5.em
  )

  val postLink = cls(
    color := "#34345C",
    &.hover(
      color := "#DD0000"
    ),
    cursor.pointer,
    marginRight := 0.5.em
  )

  val input = cls()

  val submit = cls()

  val greenText = cls(
    color := "#AF0A0F",
    fontSize := 90.pct,
    lineHeight := 2.em
  )

  val spoiler = cls(
    textDecoration.none,
    color := "#CA927B",
    background := "#CA927B",
    &.hover(
      color := "#34345C"
    )
  )

  override def toString: String = {
    "Gurochan"
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/styles/Makaba.scala version [669afb7882].









































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.karasiq.nanoboard.frontend.styles

import scalatags.Text.all._

object Makaba extends BoardStyle {
  initStyleSheet()

  val body = cls(
    color := "#333333",
    backgroundColor := "#EEEEEE"
  )

  val post = cls(
    minWidth := 40.pct,
    maxWidth := 100.pct,
    border := "solid 1px #CCCCCC",
    borderRadius := 2.px,
    display.`inline-block`,
    background := "#DDDDDD",
    color := "#333333",
    margin := 0.25.em,
    clear.both,
    padding := "0.5em 1.5em"
  )

  val postInner = cls(
    marginBottom := 0.5.em,
    fontSize := 0.9.em,
    fontFamily := "Verdana,sans-serif"
  )

  val postId = cls(
    color := "#789922",
    marginRight := 0.5.em
  )

  val postLink = cls(
    color := "#FF6600",
    &.hover(
      color := "#0066FF"
    ),
    cursor.pointer,
    marginRight := 0.5.em
  )

  val input = cls()

  val submit = cls()

  val greenText = cls(
    color.green,
    fontSize := 90.pct,
    lineHeight := 2.em
  )

  val spoiler = cls(
    textDecoration.none,
    color := "#BBBBBB",
    background := "#BBBBBB",
    &.hover(
      color := "#333333"
    )
  )

  override def toString: String = {
    "Makaba"
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/styles/Muon.scala version [9d5ad83957].









































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.karasiq.nanoboard.frontend.styles

import scalatags.Text.all._
import scalatags.generic.Style

object Muon extends BoardStyle {
  initStyleSheet()

  val body = cls(
    color := "#9B8165",
    background := "scroll #211F1A url('/img/muon_bg.jpg') repeat"
  )

  val post = cls(
    minWidth := 40.pct,
    maxWidth := 100.pct,
    border := "solid 1px #34352D",
    borderRadius := 2.px,
    display.`inline-block`,
    background := "url('/img/muon_posts.jpg') #292825",
    color := "#9B8165",
    margin := 0.25.em,
    clear.both,
    padding := "0.5em 1.5em"
  )

  val postInner = cls(
    fontFamily := "Verdana,sans-serif",
    marginBottom := 0.5.em,
    fontSize := 1.em
  )

  val postId = cls(
    color := "#789922",
    marginRight := 0.5.em
  )

  val postLink = cls(
    color := "#FFD97A",
    &.hover(
      color := "#FCE236"
    ),
    cursor.pointer,
    marginRight := 0.5.em
  )

  val input = cls(
    background := "#3E3C38 url('/img/muon_inputs.jpg') !important",
    color := "#FEC77D!important",
    border := "1px solid #44453D"
  )

  val submit = cls(
    Style("webkitAppearance", "-webkit-appearance") := "none!important",
    backgroundImage := "-webkit-gradient(linear,center top,center bottom,from(#4A4A4A),color-stop(25%,#313131),color-stop(50%,#292929),color-stop(75%,#313131),to(#4A4A4A))!important",
    backgroundColor := "#333333!important",
    borderRadius := "5px!important",
    borderBottom := "1px solid #151515!important",
    borderTop := "1px solid #151515!important",
    borderLeft := "1px solid #000000!important",
    borderRight := "1px solid #000000!important",
    color := "#AAAAAA!important"
  )

  val greenText = cls(
    color.green,
    fontSize := 90.pct,
    lineHeight := 2.em
  )

  val spoiler = cls(
    textDecoration.none,
    color := "#454545",
    background := "#454545",
    opacity := 0.5,
    &.hover(
      color := "#FFD97A"
    )
  )

  override def toString: String = {
    "Muon"
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/styles/Neutron.scala version [7cfde88325].









































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.karasiq.nanoboard.frontend.styles

import scalatags.Text.all._
import scalatags.generic.Style

object Neutron extends BoardStyle {
  initStyleSheet()
  
  val body = cls(
    color := "#698CC0",
    backgroundColor := "#212121"
  )

  val post = cls(
    minWidth := 40.pct,
    maxWidth := 100.pct,
    border := "solid 1px #575757",
    borderRadius := 2.px,
    display.`inline-block`,
    background := "#212121",
    color := "#698CC0",
    margin := 0.25.em,
    clear.both,
    padding := "0.5em 1.5em"
  )

  val postInner = cls(
    fontFamily := "Trebuchet MS,Trebuchet,tahoma,serif",
    marginBottom := 0.5.em,
    fontSize := 1.em
  )

  val postId = cls(
    color := "#789922",
    marginRight := 0.5.em
  )

  val postLink = cls(
    color := "#C9BE89",
    &.hover(
      color := "#EEFEBB"
    ),
    cursor.pointer,
    marginRight := 0.5.em
  )

  val input = cls(
    backgroundColor := "#111111 !important",
    color := "#CCCCCC",
    border := "2px solid #545454!important"
  )

  val submit = cls(
    Style("webkitAppearance", "-webkit-appearance") := "none!important",
    backgroundColor := "#333333!important",
    backgroundImage := "-webkit-gradient(linear,center top,center bottom,from(#4A4A4A),color-stop(25%,#313131),color-stop(50%,#292929),color-stop(75%,#313131),to(#4A4A4A))!important",
    borderRadius := "5px!important",
    borderBottom := "1px solid #151515!important",
    borderTop := "1px solid #151515!important",
    borderLeft := "1px solid #000000!important",
    borderRight := "1px solid #000000!important",
    color := "#AAAAAA!important",
    fontWeight := "bold!important"
  )

  val greenText = cls(
    color.green,
    fontSize := 90.pct,
    lineHeight := 2.em
  )

  val spoiler = cls(
    textDecoration.none,
    color := "#575757",
    background := "#575757",
    &.hover(
      color := "#C9BE89"
    )
  )

  override def toString: String = {
    "Neutron"
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/utils/Blobs.scala version [5d2de217eb].









































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.karasiq.nanoboard.frontend.utils

import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.scalajs.js
import scala.scalajs.js.typedarray.Uint8Array

import org.scalajs.dom
import org.scalajs.dom.{Blob, Event}
import org.scalajs.dom.raw._
import scalatags.JsDom.all._

/**
  * Blob/file utility
  */
object Blobs {

  def fromBytes(data: Array[Byte], contentType: String = ""): Blob = {
    import scala.scalajs.js.JSConverters._
    val array = new Uint8Array(data.toJSArray)
    new Blob(js.Array(array), BlobPropertyBag(contentType))
  }

  def fromChars(data: Array[Char], contentType: String = ""): Blob = {
    fromBytes(data.map(_.toByte), contentType)
  }

  def fromString(data: String, contentType: String = ""): Blob = {
    fromChars(data.toCharArray, contentType)
  }

  def fromBase64(base64: String, contentType: String = ""): Blob = {
    fromString(dom.window.atob(base64), contentType)
  }

  def saveBlob(blob: Blob, fileName: String): Unit = {
    val url = URL.createObjectURL(blob)
    val anchor = a(href := url, attr("download") := fileName, target := "_blank", display.none).render
    dom.document.body.appendChild(anchor)
    dom.window.setTimeout(() ⇒ {
      dom.document.body.removeChild(anchor)
      URL.revokeObjectURL(url)
    }, 500)
    anchor.click()
  }

  def asUrl(blob: Blob): String = {
    URL.createObjectURL(blob)
  }

  def asDataURL(blob: Blob): Future[String] = {
    val promise = Promise[String]
    val reader = new FileReader
    reader.readAsDataURL(blob)
    reader.onloadend = (_: ProgressEvent) ⇒ {
      promise.success(reader.result.asInstanceOf[String])
    }

    reader.onerror = (e: Event) ⇒ {
      promise.failure(new IllegalArgumentException(e.toString))
    }

    promise.future
  }

  def asBase64(blob: Blob)(implicit ec: ExecutionContext): Future[String] = {
    asDataURL(blob).map(_.split(",", 2).last)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/utils/CancelledException.scala version [dd91e190e1].







>
>
>
1
2
3
package com.karasiq.nanoboard.frontend.utils

case object CancelledException extends Exception("Operation is cancelled")

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/utils/Images.scala version [557b5cc06e].





































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.karasiq.nanoboard.frontend.utils

import scala.concurrent.{Future, Promise}
import scala.scalajs.js
import scala.scalajs.js.annotation.JSGlobal

import org.scalajs.dom.{Blob, ErrorEvent}

@js.native
private[utils] trait ImageUtil extends js.Object {
  def sharpen(ctx: js.Dynamic, width: Int, height: Int, sharpness: Double): Unit = js.native
  def drawImage(file: Blob, compress: Boolean = true, format: String = "image/jpeg", scale: Double, quality: Double, sharpness: Double, success: js.Function, error: js.Function): Unit = js.native
}

object Images {
  @js.native
  @JSGlobal("img2base64")
  private object ImageUtil extends ImageUtil

  def compress(data: Blob, format: String = "image/jpeg", scale: Int = 100, quality: Int = 100, sharpness: Int = 100): Future[String] = {
    assert(sharpness > 0 && scale > 0 && quality > 0 && quality <= 100)
    val promise = Promise[String]
    ImageUtil.drawImage(data, compress = true, format, scale, quality, sharpness, { (url: String) ⇒
      promise.success(url.split(",", 2).last)
    }, { (e: ErrorEvent) ⇒
      promise.failure(new IllegalArgumentException(e.message))
    })
    promise.future
  }

  def isWebpSupported: Boolean = {
    js.Dynamic.global.Modernizr.webp.toString == "true" // Boolean bug
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/utils/Mouse.scala version [e5ede7f7ac].













































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.karasiq.nanoboard.frontend.utils

import scala.scalajs.js

import org.scalajs.dom
import org.scalajs.dom.{Element, MouseEvent}
import scalatags.JsDom.all._

object Mouse {
  def scroll(selector: String): Boolean = {
    import org.scalajs.jquery.jQuery
    val e = jQuery(selector)
    if (e.length > 0) {
      jQuery("html, body").animate(js.Dynamic.literal(
        scrollTop = e.offset().asInstanceOf[js.Dynamic].top
      ), 800)
      true
    } else {
      false
    }
  }

  def relative(xOffset: Double = 0, yOffset: Double = 0): Modifier = {
    Seq[Modifier](
      position.fixed,
      overflow.hidden,
      new Modifier {
        override def applyTo(t: Element): Unit = {
          dom.window.addEventListener("mousemove", (e: MouseEvent) ⇒ {
            val style = t.asInstanceOf[dom.html.Element].style
            style.left = (e.clientX + xOffset) + "px"
            style.top = (e.clientY + yOffset) + "px"
          })
        }
      }
    )
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/utils/Notifications.scala version [730817346b].





























































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package com.karasiq.nanoboard.frontend.utils

import java.io.{PrintWriter, StringWriter}

import org.scalajs.dom.console

// From proxychecker frontend
object Notifications {
  import scala.scalajs.js.{Array ⇒ JsArray}
  import scala.scalajs.js.Dynamic.{global ⇒ js, literal ⇒ lt}

  private def defaultTimeout: Int = {
    1800
  }

  sealed trait Layout {
    def apply(): String
  }

  object Layout {
    private final class LayoutImpl(string: String) extends Layout {
      override def apply(): String = string
    }

    def top: Layout = new LayoutImpl("top")
    def topLeft: Layout = new LayoutImpl("topLeft")
    def topRight: Layout = new LayoutImpl("topRight")
    def topCenter: Layout = new LayoutImpl("topCenter")

    def center: Layout = new LayoutImpl("center")
    def centerLeft: Layout = new LayoutImpl("centerLeft")
    def centerRight: Layout = new LayoutImpl("centerRight")

    def bottom: Layout = new LayoutImpl("bottom")
    def bottomLeft: Layout = new LayoutImpl("bottomLeft")
    def bottomRight: Layout = new LayoutImpl("bottomRight")
    def bottomCenter: Layout = new LayoutImpl("bottomCenter")
  }

  sealed class InfoMessage(`type`: String) {
    def apply(text: String, layout: Layout = Layout.top, timeout: Int = defaultTimeout): Unit = {
      Notifications.notify(text, `type`=`type`, layout=layout, timeout = Some(timeout))
    }
  }

  sealed class ErrorMessage(cause: Option[Throwable]) extends InfoMessage("error") {
    private def formatException(title: String, exc: Option[Throwable]): String = {
      val stringWriter = new StringWriter(256)
      val printWriter = new PrintWriter(stringWriter)
      try {
        printWriter.println(title)
        exc.foreach(_.printStackTrace(printWriter))
        printWriter.flush()
        stringWriter.toString
      } finally printWriter.close()
    }

    override def apply(text: String, layout: Layout = Layout.top, timeout: Int = defaultTimeout): Unit = {
      val formatted = formatException(text, cause)
      console.error(formatted)
      super.apply(formatted, layout, timeout)
    }
  }

  def alert: InfoMessage = new InfoMessage("alert")
  def success: InfoMessage = new InfoMessage("success")
  def error(cause: Throwable): InfoMessage = new ErrorMessage(Some(cause))
  def warning: InfoMessage = new InfoMessage("warning")
  def info: InfoMessage = new InfoMessage("information")

  sealed class ConfirmationMessage {
    def apply(text: String, layout: Layout = Layout.top)(onSuccess: ⇒ Unit): Unit = {
      Notifications.notify(text, `type`="confirmation", layout=layout, buttons=JsArray(
        button("Ok", "btn btn-primary") { msg ⇒
          msg.close()
          onSuccess
        },

        button("Cancel", "btn btn-danger") { msg ⇒
          msg.close()
        }
      ))
    }
  }

  def confirmation = new ConfirmationMessage

  private def button(text: String, addClass: String = "")(onClick: scalajs.js.Dynamic ⇒ Unit): scalajs.js.Dynamic = {
    lt(addClass=addClass, text=text, onClick=onClick)
  }

  private def notify(text: String, `type`: String = "alert", theme: String = "defaultTheme",
                     layout: Layout = Layout.top, timeout: Option[Int] = None,
                     buttons: JsArray[scalajs.js.Dynamic] = JsArray()) = {
    js.noty(lt(
      `type`=`type`,
      text = text,
      layout = layout(),
      theme = theme,
      timeout = if (timeout.nonEmpty) timeout.get else false,
      animation = lt(
        open = lt(height="toggle"),
        close = lt(height="toggle"),
        easing = "swing", // easing
        speed = 500 // opening & closing animation speed
      ),
      buttons = if (buttons.nonEmpty) buttons else false
    ))
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/utils/PostParser.scala version [3aefb0d862].

















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.karasiq.nanoboard.frontend.utils

import org.parboiled2._

import com.karasiq.nanoboard.frontend.utils.PostDomValue._

sealed trait PostDomValue
object PostDomValue {
  case class PostDomValues(seq: Seq[PostDomValue]) extends PostDomValue
  case class PlainText(underlying: String) extends PostDomValue
  case class BBCode(name: String, parameters: Map[String, String], inner: PostDomValue) extends PostDomValue
  case class ShortBBCode(name: String, value: String) extends PostDomValue
}

object PostParser {
  def parse(text: String, plainCodes: Set[String] = Set("md", "plain", "code", "file", "img", "video")): PostDomValue = {
    new PostParser(text, plainCodes).Message.run().getOrElse(PlainText(text))
  }
}

class PostParser(val input: ParserInput, plainCodes: Set[String]) extends Parser {
  private def BBCodeParameter: Rule1[(String, String)] = rule { capture(oneOrMore(CharPredicate.Alpha)) ~ (('=' ~ '"' ~ capture(zeroOrMore(!'"' ~ ANY)) ~ '"') | push("")) ~> { (s1: String, s2: String) ⇒ s1 → s2 } }
  private def BBCodeParameters: Rule1[Map[String, String]] = rule { zeroOrMore(' ' ~ BBCodeParameter) ~> { (parameters: Seq[(String, String)]) ⇒ parameters.toMap } }
  private def BBCodeAnyTag: Rule0 = rule { '[' ~ optional('/') ~ oneOrMore(CharPredicate.Alpha) ~ (('=' ~ oneOrMore(!']' ~ ANY)) | BBCodeParameters) ~ ']' }
  private def BBCodeOpenTag: Rule2[String, Map[String, String]] = rule { '[' ~ capture(oneOrMore(CharPredicate.Alpha)) ~ BBCodeParameters ~ ']' }
  private def BBCodeCloseTag(tag: String): Rule0 = rule { "[/" ~ tag ~ "]" }

  def BBCode: Rule1[PostDomValue.BBCode] = rule {
    BBCodeOpenTag ~>
    { (tag: String, parameters: Map[String, String]) ⇒
      push(tag) ~ push(parameters) ~
      ((test(plainCodes.contains(tag.toLowerCase) || parameters.contains("plain")) ~ capture(oneOrMore(!BBCodeCloseTag(tag) ~ ANY)) ~> PostDomValue.PlainText) | FormattedText) ~ BBCodeCloseTag(tag)
    } ~> PostDomValue.BBCode
  }

  def ShortBBCode: Rule1[PostDomValue.ShortBBCode] = rule { '[' ~ capture(oneOrMore(CharPredicate.Alpha)) ~ '=' ~ capture(oneOrMore(!']' ~ ANY)) ~ ']' ~> PostDomValue.ShortBBCode }
  def PlainText: Rule1[PostDomValue.PlainText] = rule { capture(oneOrMore(!BBCodeAnyTag ~ ANY)) ~> PostDomValue.PlainText }
  def FormattedText: Rule1[PostDomValues] = rule { zeroOrMore(BBCode | ShortBBCode | PlainText) ~> PostDomValues }
  def Message = rule { FormattedText ~ EOI }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/frontend/src/main/scala/com/karasiq/nanoboard/frontend/utils/RxLocation.scala version [c2182edec3].



































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.karasiq.nanoboard.frontend.utils

import scala.scalajs.js.UndefOr

import org.scalajs.dom
import org.scalajs.dom.window
import org.scalajs.jquery.jQuery
import rx._

final class RxLocation(implicit ctx: Ctx.Owner) {
  private def readHash(hash: UndefOr[String]): Option[String] = {
    hash
      .filter(_.nonEmpty)
      .map(_.tail)
      .toOption
  }

  val hash: Var[Option[String]] = Var(readHash(window.location.hash))

  hash.triggerLater {
    window.location.hash = hash.now.fold("")("#" + _)
  }

  jQuery(dom.window).on("hashchange", () ⇒ {
    hash() = readHash(window.location.hash)
  })
}

object RxLocation {
  def apply()(implicit ctx: Ctx.Owner): RxLocation = {
    new RxLocation()
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/images/screenshot.png version [21aa3fb705].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/resources/reference.conf version [479a20a76f].

































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nanoboard {
  version = 1.3.2
  client-version = karasiq-nanoboard v${nanoboard.version}
  encryption-key = "nano3"
  bitmessage {
    chan-address = "BM-2cWzzuoiF7pxchwiF5obVmXaQKFywETu7k"
  }

  message-pack-format = text
  pow {
    version = v1
    offset = 3
    length = 3
    threshold = 1
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/NanoboardCategory.scala version [a5613fd097].

















>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
package com.karasiq.nanoboard

/**
  * Nanoboard category data
  * @param hash Category hash
  * @param name Category alias
  */
case class NanoboardCategory(hash: String, name: String)

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/NanoboardLegacy.scala version [f5f5d1ea33].



































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.karasiq.nanoboard

import java.io.File

/**
  * Official implementation compatibility util
  * @see [[https://github.com/nanoboard/nanoboard]]
  */
object NanoboardLegacy {
  /**
    * Reads places.txt file in `nanoboard/1.*` client format
    * @param file File path
    */
  def placesFromTxt(file: String): Vector[String] = {
    if (new File(file).isFile) {
      val source = io.Source.fromFile(file, "UTF-8")
      try {
        source.getLines()
          .filter(_.nonEmpty)
          .toVector
      } finally source.close()
    } else {
      Vector.empty
    }
  }

  /**
    * Reads categories.txt file in `nanoboard/1.*` client format
    * @param file File path
    */
  def categoriesFromTxt(file: String): Vector[NanoboardCategory] = {
    if (new File(file).isFile) {
      val source = io.Source.fromFile(file, "UTF-8")
      try {
        source
          .getLines()
          .filter(_.nonEmpty)
          .grouped(2)
          .collect {
            case Seq(hash, name) ⇒
              NanoboardCategory(hash, name)
          }
          .toVector
      } finally source.close()
    } else {
      Vector.empty
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/NanoboardMessage.scala version [55666e7c7f].





















































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.karasiq.nanoboard

import scala.util.Try

import akka.util.ByteString

import com.karasiq.nanoboard.encoding.NanoboardCrypto.{sha256, BCDigestOps}
import com.karasiq.nanoboard.encoding.formats.{CBORMessagePackFormat, MessagePackFormat, TextMessagePackFormat}
import com.karasiq.nanoboard.utils.{ByteStringOps, _}

case class NanoboardMessage(parent: String, text: String, pow: ByteString = NanoboardMessage.NoPOW, signature: ByteString = NanoboardMessage.NoSignature) {
  val hash: String = sha256.digest(ByteString(parent + NanoboardMessage.textWithSignatureTags(this))).take(16).toHexString()
}

object NanoboardMessage extends MessagePackFormat {
  // Constants
  val HashLength = 32
  val HashFormat = "(?i)[a-f0-9]{32}".r
  val POWLength = 128
  val SignatureLength = 64

  val NoPOW = ByteString(Array.fill[Byte](POWLength)(0))
  val NoSignature = ByteString(Array.fill[Byte](SignatureLength)(0))

  //noinspection ScalaDeprecation
  override def parseMessages(payload: ByteString): Vector[NanoboardMessage] = {
    Try(CBORMessagePackFormat.parseMessages(payload))
      .orElse(Try(TextMessagePackFormat.parseMessages(payload)))
      .get
  }

  override def writeMessages(messages: Seq[NanoboardMessage]): ByteString = {
    TextMessagePackFormat.writeMessages(messages) // CBORMessagePackFormat.writeMessages(messages)
  }

  private[nanoboard] def getPOWTag(pow: ByteString) = {
    if (pow.isEmpty || pow == NoPOW) "" else s"[pow=${pow.toHexString()}]"
  }

  private[nanoboard] def getSignatureTag(signature: ByteString) = {
    if (signature.isEmpty || signature == NoSignature) "" else s"[sign=${signature.toHexString()}]"
  }

  private[nanoboard] def stripSignatureTags(message: String): (String, Option[ByteString], Option[ByteString]) = {
    val regex = "(?i)\\[pow=([0-9a-f]+)\\]\\[sign=([0-9a-f]{128})\\]".r
    regex.findFirstMatchIn(message) match {
      case Some(m @ regex(pow, sign)) ⇒
        (message.take(m.start), Some(ByteString.fromHexString(pow)), Some(ByteString.fromHexString(sign)))

      case _ ⇒
        (message, None, None)
    }
  }

  private[nanoboard] def textWithSignatureTags(m: NanoboardMessage): String = {
    m.text + getPOWTag(m.pow) + getSignatureTag(m.signature)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/NanoboardMessageGenerator.scala version [d5ee3872f0].

















































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.karasiq.nanoboard

import java.time.format.{DateTimeFormatter, DateTimeFormatterBuilder, TextStyle}
import java.time.temporal.ChronoField
import java.time.{ZoneId, ZonedDateTime}
import java.util.Locale

import com.typesafe.config.{Config, ConfigFactory}

import scala.util.Try

object NanoboardMessageGenerator {
  def fromConfig(config: Config) = {
    new NanoboardMessageGenerator(config.getString("client-version"), Try(ZoneId.of(config.getString("default-time-zone"))).getOrElse(ZoneId.systemDefault()))
  }

  def apply(config: Config = ConfigFactory.load()) = {
    fromConfig(config.getConfig("nanoboard"))
  }
}

/**
  * Nanoboard message generator
  * @param clientVersion Client version string
  * @param timeZone Timestamp time zone
  */
class NanoboardMessageGenerator(clientVersion: String, timeZone: ZoneId) {
  /**
    * Message timestamp format
    */
  protected val timestampFormat = new DateTimeFormatterBuilder()
    .appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
    .appendLiteral(", ")
    .appendValue(ChronoField.DAY_OF_MONTH)
    .appendLiteral('/')
    .appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT)
    .appendLiteral('/')
    .appendValue(ChronoField.YEAR)
    .appendLiteral(", ")
    .append(DateTimeFormatter.ISO_LOCAL_TIME)
    .appendLiteral(" (")
    .appendZoneOrOffsetId()
    .appendLiteral(")")
    .toFormatter(Locale.ENGLISH)

  /**
    * Creates new message with timestamp and client header
    * @param parent Parent message hash
    * @param text Message text
    * @return Created message
    */
  def newMessage(parent: String, text: String): NanoboardMessage = {
    val header = s"[g]${timestampFormat.format(ZonedDateTime.now(timeZone))}, client: $clientVersion[/g]"
    NanoboardMessage(parent, s"$header\n$text")
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/captcha/NanoboardCaptcha.scala version [bd706c6666].



















































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package com.karasiq.nanoboard.captcha

import java.awt.Color
import java.awt.image.BufferedImage

import scala.concurrent.{ExecutionContext, Future}

import akka.util.ByteString

import com.karasiq.nanoboard.NanoboardMessage
import com.karasiq.nanoboard.captcha.internal.{Constants, Ed25519}
import com.karasiq.nanoboard.captcha.storage.NanoboardCaptchaSource
import com.karasiq.nanoboard.encoding.NanoboardCrypto._
import com.karasiq.nanoboard.utils._

/**
  * Nanoboard captcha block
  * @param publicKey EdDSA public key
  * @param seed XORed seed
  * @param image Encoded captcha image
  * @see [[https://github.com/nanoboard/nanoboard/commit/ef747596802919c270d0de61bd9bcdf319c787f0]]
  * @note {{{
  *   UnsignedMessage = ReplyTo + Text + PowValue
  *   Signature = Sign(UnsignedMessage, PrivateKeyFromSeed(DecryptedSeed))
  * }}}
  */
case class NanoboardCaptcha(publicKey: ByteString, seed: ByteString, image: ByteString) {
  require(publicKey.length == Constants.PUBLIC_KEY_LENGTH && seed.length == Constants.SEED_LENGTH && image.length == Constants.IMAGE_LENGTH, "Invalid data")

  def toBytes: ByteString = {
    publicKey ++ seed ++ image
  }

  private def decryptSeed(answer: String): ByteString = {
    val result = new Array[Byte](seed.length)
    val hashedAnswer = sha512.digest(ByteString(answer + publicKey.toHexString())) // Hex string is lowercase
    for (i ← seed.indices) {
      result(i) = (seed(i) ^ hashedAnswer(i & 63)).toByte
    }
    ByteString(result)
  }

  /**
    * Calculates the signature for post
    * @param post Unsigned message
    * @param guess Captcha answer
    * @return EdDSA digital signature
    */
  def signature(post: ByteString, guess: String): ByteString = {
    val privateKey = Ed25519.privateKey(decryptSeed(guess))
    Ed25519.sign(privateKey, post)
  }

  /**
    * Verifies the signature for post
    * @param post Unsigned message
    * @param signature EdDSA digital signature
    * @return Is signature valid
    */
  def verify(post: ByteString, signature: ByteString): Boolean = {
    Ed25519.verify(Ed25519.publicKey(publicKey), post, signature)
  }
}

/**
  * Nanoboard captcha utility
  */
object NanoboardCaptcha {
  /**
    * Reads captcha block from bytes
    * @param byteString Encoded captcha block
    */
  def fromBytes(byteString: ByteString): NanoboardCaptcha = {
    assert(byteString.length == Constants.BLOCK_LENGTH, "Invalid captcha block length")
    NanoboardCaptcha(byteString.take(Constants.PUBLIC_KEY_LENGTH),
      byteString.drop(Constants.PUBLIC_KEY_LENGTH).take(Constants.SEED_LENGTH),
      byteString.drop(Constants.PUBLIC_KEY_LENGTH + Constants.SEED_LENGTH).take(Constants.IMAGE_LENGTH))
  }

  /**
    * Verifies POW and signature of the message
    * @param message Signed message
    * @param pow Proof-of-work calculator
    * @param captcha Captcha storage
    * @param ec Execution context
    * @return Is message valid
    */
  def verify(message: NanoboardMessage, pow: NanoboardPow, captcha: NanoboardCaptchaSource)(implicit ec: ExecutionContext): Future[Boolean] = {
    Future.reduce(Seq(
      Future(pow.verify(message)),
      captcha(pow.getCaptchaIndex(message, captcha.length)).map(_.verify(pow.getSignPayload(message), message.signature))
    ))(_ && _)
  }

  private def renderBufferedImage(captcha: NanoboardCaptcha, width: Int = 50, height: Int = 20): BufferedImage = {
    var bii, byi = 0
    val image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
    for (x ← 0 until width; y ← 0 until height) {
      val color = if ((captcha.image(byi) & (1 << bii).toByte) != 0) Color.BLACK else Color.WHITE
      bii += 1
      if (bii >= 8) {
        bii = 0
        byi += 1
      }
      image.setRGB(x, y, color.getRGB)
    }
    image
  }

  /**
    * Renders encoded captcha image to png
    * @param captcha Encoded captcha image
    * @param width Output width
    * @param height Output height
    * @return Captcha image, rendered as png
    */
  def render(captcha: NanoboardCaptcha, width: Int = 50, height: Int = 20): ByteString = {
    renderBufferedImage(captcha, width, height).toBytes("png")
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/captcha/NanoboardPow.scala version [711a64017e].















































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.karasiq.nanoboard.captcha

import java.util.concurrent.Executors

import scala.concurrent.{ExecutionContext, Future}

import akka.util.ByteString
import com.typesafe.config.{Config, ConfigFactory}

import com.karasiq.nanoboard.NanoboardMessage
import com.karasiq.nanoboard.captcha.impl.{NanoboardPowV1, NanoboardPowV2}

trait NanoboardPow {
  /**
    * Verifies the message proof-of-work value
    * @param message Message with calculated POW
    * @return Is POW valid
    */
  def verify(message: NanoboardMessage): Boolean

  /**
    * Calculates nanoboard proof-of-work value
    * @param message Message without a calculated POW
    * @return Proof-of-work value
    */
  def calculate(message: NanoboardMessage): Future[ByteString]

  /**
    * Calculates captcha index
    * @param message Unsigned message
    * @param max Maximum captcha index
    * @return Index of the captcha to be solved
    */
  def getCaptchaIndex(message: NanoboardMessage, max: Int): Int

  /**
    * Data to sign/verify with the EdDSA digital signature
    * @param message Message
    * @return ReplyTo + Text + PowValue
    */
  def getSignPayload(message: NanoboardMessage): ByteString
}

object NanoboardPow {
  /**
    * Creates nanoboard POW calculator from config
    * @param config Configuration object
    * @return Proof-of-work calculator
    */
  def apply(config: Config = ConfigFactory.load())(implicit ec: ExecutionContext): NanoboardPow = {
    val offset = config.getInt("nanoboard.pow.offset")
    val length = config.getInt("nanoboard.pow.length")
    val threshold = config.getInt("nanoboard.pow.threshold")

    config.getString("nanoboard.pow.version") match {
      case "v1" ⇒
        new NanoboardPowV1(offset, length, threshold)

      case "v2" ⇒
        new NanoboardPowV2(offset, length, threshold)
    }
  }

  /**
    * Provides execution context, optimised for nanoboard proof-of-work calculation
    * @return Work stealing pool execution context
    */
  def executionContext() = {
    ExecutionContext.fromExecutorService(Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors()))
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/captcha/impl/NanoboardPowV1.scala version [f8acc224cf].













































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package com.karasiq.nanoboard.captcha.impl

import java.util.concurrent.RejectedExecutionException

import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.Random

import akka.util.ByteString
import org.bouncycastle.crypto.digests.SHA256Digest

import com.karasiq.nanoboard.NanoboardMessage
import com.karasiq.nanoboard.captcha.NanoboardPow
import com.karasiq.nanoboard.encoding.NanoboardCrypto._
import com.karasiq.nanoboard.utils.ByteStringOps

private object NanoboardPowV1 {
  def withoutXMG(text: String) = {
    val regex = "\\[xmg=[^\\]]*\\]".r
    regex.replaceAllIn(text, { rm ⇒
      sha256.digest(ByteString(rm.group(0))).toHexString()
    })
  }
}

/**
  * Nanoboard proof-of-work calculator
  * @param offset Hash offset for bytes verification
  * @param length Required consequent bytes
  * @param threshold Maximum byte value
  * @see [[https://github.com/nanoboard/nanoboard/commit/ef747596802919c270d0de61bd9bcdf319c787f0]]
  * @note {{{
  *   PowValue = "[pow=$Hex(RandomBytes(128))]"
  *   PowHash = SHA256(ReplyTo + Text + PowValue)
  * }}}
  */
final class NanoboardPowV1(offset: Int, length: Int, threshold: Int)(implicit ec: ExecutionContext) extends NanoboardPow {
  // For verification with cached SHA256 state
  private def verify(update: ByteString, md: SHA256Digest): Boolean = {
    val hash = md.digest(update)
    var maxLength = 0
    for (i ← offset until hash.length if maxLength < this.length) {
      if (java.lang.Byte.toUnsignedInt(hash(i)) <= threshold)
        maxLength += 1
      else
        maxLength = 0
    }
    maxLength >= this.length
  }

  /**
    * Verifies the message proof-of-work value
    * @param message Message with calculated POW
    * @return Is POW valid
    */
  def verify(message: NanoboardMessage): Boolean = {
    verify(getVerifyPayload(message), sha256)
  }

  /**
    * Calculates nanoboard proof-of-work value
    * @param message Message without a calculated POW
    * @return `[pow]` tag to be appended to message
    */
  def calculate(message: NanoboardMessage): Future[ByteString] = {
    val payload = getPOWPayload(message)
    val open = ByteString("[pow=")
    val preHashed = sha256.updated(payload ++ open)
    val close = ByteString("]")
    val result = Promise[ByteString]

    def submitTasks(): Unit = {
      try {
        Future.sequence(for (_ ← 0 to 100) yield Future {
          val array = Array.ofDim[Byte](NanoboardMessage.POWLength)
          Random.nextBytes(array)
          val powValue = ByteString(array)
          val powHexString = ByteString(powValue.toHexString())
          if (verify(powHexString ++ close, new SHA256Digest(preHashed))) {
            result.success(powValue)
          }
        }).foreach { _ ⇒
          if (!result.isCompleted) {
            submitTasks()
          }
        }
      } catch {
        case _: RejectedExecutionException ⇒
        // Pass

        case e: Throwable ⇒
          result.failure(e)
      }
    }

    submitTasks()
    result.future
  }

  def getCaptchaIndex(message: NanoboardMessage, max: Int): Int = {
    sha256.digest(getVerifyPayload(message)).take(3).map(java.lang.Byte.toUnsignedInt) match {
      case Seq(b0, b1, b2) ⇒
        (b0 + b1 * 256 + b2 * 256 * 256) % max
    }
  }

  def getSignPayload(message: NanoboardMessage) = {
    ByteString(message.parent + message.text + NanoboardMessage.getPOWTag(message.pow))
  }

  private[this] def getPOWPayload(message: NanoboardMessage) = {
    ByteString(message.parent + NanoboardPowV1.withoutXMG(message.text))
  }

  private[this] def getVerifyPayload(message: NanoboardMessage) = {
    getPOWPayload(message) ++ ByteString(NanoboardMessage.getPOWTag(message.pow))
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/captcha/impl/NanoboardPowV2.scala version [005ddf73b3].









































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.karasiq.nanoboard.captcha.impl

import java.util.concurrent.RejectedExecutionException

import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.Random

import akka.util.ByteString
import org.bouncycastle.crypto.digests.SHA256Digest

import com.karasiq.nanoboard.NanoboardMessage
import com.karasiq.nanoboard.captcha.NanoboardPow
import com.karasiq.nanoboard.encoding.NanoboardCrypto.{sha256, BCDigestOps}

/**
  * Nanoboard proof-of-work calculator
  * @param offset Hash offset for bytes verification
  * @param length Required consequent bytes
  * @param threshold Maximum byte value
  * @see [[https://github.com/nanoboard/nanoboard/commit/ef747596802919c270d0de61bd9bcdf319c787f0]]
  * @note {{{
  *   PowValue = RandomBytes(128)
  *   PowHash = SHA256(ReplyTo + Text + PowValue)
  * }}}
  */
final class NanoboardPowV2(offset: Int, length: Int, threshold: Int)(implicit ec: ExecutionContext) extends NanoboardPow {
  // For verification with cached SHA256 state
  private def verify(bytes: ByteString, md: SHA256Digest): Boolean = {
    val hash = md.digest(bytes)
    var maxLength = 0
    for (i ← offset until hash.length if maxLength < this.length) {
      if (java.lang.Byte.toUnsignedInt(hash(i)) <= threshold)
        maxLength += 1
      else
        maxLength = 0
    }
    maxLength >= this.length
  }

  /**
    * Verifies the message proof-of-work value
    * @param message Message with calculated POW
    * @return Is POW valid
    */
  def verify(message: NanoboardMessage): Boolean = {
    verify(getSignPayload(message), sha256)
  }

  /**
    * Calculates nanoboard proof-of-work value
    * @param message Message without a calculated POW
    * @return Proof-of-work value
    */
  def calculate(message: NanoboardMessage): Future[ByteString] = {
    val payload = getPOWPayload(message)
    val preHashed = sha256.updated(payload)
    val result = Promise[ByteString]

    def submitTasks(): Unit = {
      try {
        Future.sequence(for (_ ← 0 to 100) yield Future {
          val array = Array.ofDim[Byte](NanoboardMessage.POWLength)
          Random.nextBytes(array)
          val data = ByteString(array)
          if (verify(data, new SHA256Digest(preHashed))) {
            result.success(data)
          }
        }).foreach { _ ⇒
          if (!result.isCompleted) {
            submitTasks()
          }
        }
      } catch {
        case _: RejectedExecutionException ⇒
          // Pass

        case e: Throwable ⇒
          result.failure(e)
      }
    }

    submitTasks()
    result.future
  }

  def getCaptchaIndex(message: NanoboardMessage, max: Int): Int = {
    sha256.digest(getSignPayload(message)).take(3).map(java.lang.Byte.toUnsignedInt) match {
      case Seq(b0, b1, b2) ⇒
        (b0 + b1 * 256 + b2 * 256 * 256) % max
    }
  }

  def getSignPayload(message: NanoboardMessage): ByteString = {
    getPOWPayload(message) ++ message.pow
  }

  private[this] def getPOWPayload(message: NanoboardMessage) = {
    ByteString(message.parent + message.text)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/captcha/internal/Constants.scala version [6210998a75].



















>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
package com.karasiq.nanoboard.captcha.internal

private[captcha] object Constants {
  // Captcha pack sizes
  val PUBLIC_KEY_LENGTH = 32
  val SEED_LENGTH = 32
  val IMAGE_LENGTH = 125
  val BLOCK_LENGTH = PUBLIC_KEY_LENGTH + SEED_LENGTH + IMAGE_LENGTH // 189
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/captcha/internal/Ed25519.scala version [8e39782c25].



































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.karasiq.nanoboard.captcha.internal

import java.util.function.Supplier

import akka.util.ByteString
import net.i2p.crypto.eddsa.{EdDSAEngine, EdDSAPrivateKey, EdDSAPublicKey}
import net.i2p.crypto.eddsa.spec.{EdDSANamedCurveTable, EdDSAPrivateKeySpec, EdDSAPublicKeySpec}

/**
  * Ed25519 digital signature utility
  */
private[captcha] object Ed25519 {
  /**
    * Default curve specification
    */
  private val curve25519 = EdDSANamedCurveTable.getByName("Ed25519")

  //noinspection ConvertExpressionToSAM
  private val tlEngine = ThreadLocal.withInitial(new Supplier[EdDSAEngine] {
    override def get(): EdDSAEngine = new EdDSAEngine()
  })

  /**
    * Reads encoded public key
    * @param data Public key data
    * @return EdDSA public key
    */
  def publicKey(data: ByteString): EdDSAPublicKey = {
    new EdDSAPublicKey(new EdDSAPublicKeySpec(data.toArray[Byte], curve25519))
  }

  /**
    * Reads private key from seed
    * @param seed Private key 32-byte seed
    * @return EdDSA private key
    */
  def privateKey(seed: ByteString): EdDSAPrivateKey = {
    new EdDSAPrivateKey(new EdDSAPrivateKeySpec(seed.toArray[Byte], curve25519))
  }

  /**
    * Verifies EdDSA digital signature with the provided key
    * @param key EdDSA public key
    * @param data Signed data
    * @param signature EdDSA digital signature
    * @return Is signature valid
    */
  def verify(key: EdDSAPublicKey, data: ByteString, signature: ByteString): Boolean = {
    val engine = tlEngine.get()
    engine.initVerify(key)
    engine.verifyOneShot(data.toArray, signature.toArray[Byte])
  }

  /**
    * Signs data with the provided key
    * @param key EdDSA private key
    * @param data Data to sign
    * @return EdDSA digital signature
    */
  def sign(key: EdDSAPrivateKey, data: ByteString): ByteString = {
    val engine = tlEngine.get()
    engine.initSign(key)
    ByteString(engine.signOneShot(data.toArray))
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/captcha/storage/NanoboardCaptchaFileSource.scala version [d1fa92fd09].

































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.karasiq.nanoboard.captcha.storage

import java.io.{Closeable, RandomAccessFile}
import java.util.concurrent.Executors

import akka.util.ByteString
import com.karasiq.nanoboard.captcha.NanoboardCaptcha
import com.karasiq.nanoboard.captcha.internal.Constants
import org.apache.commons.io.IOUtils

import scala.concurrent.{ExecutionContext, Future}

/**
  * Nanoboard captcha file storage
  * @param file File path
  */
final class NanoboardCaptchaFileSource(file: String) extends NanoboardCaptchaSource with Closeable {
  private implicit val ec = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor())
  private val randomAccessFile = new RandomAccessFile(file, "r")
  private val buffer = new Array[Byte](Constants.BLOCK_LENGTH)

  def apply(index: Int): Future[NanoboardCaptcha] = {
    try {
      assert(index < this.length, s"Invalid index: $index, captcha blocks: $length")
      Future {
        randomAccessFile.seek(index.toLong * Constants.BLOCK_LENGTH)
        randomAccessFile.read(buffer, 0, Constants.BLOCK_LENGTH)
        NanoboardCaptcha.fromBytes(ByteString(buffer))
      }
    } catch {
      case e: Throwable ⇒
        Future.failed(e)
    }
  }

  override val length: Int = {
    val length = randomAccessFile.length() / Constants.BLOCK_LENGTH
    math.min(length, Int.MaxValue).toInt
  }

  /**
    * Closes this file
    */
  override def close(): Unit = {
    ec.shutdownNow()
    IOUtils.closeQuietly(randomAccessFile)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/captcha/storage/NanoboardCaptchaMemorySource.scala version [c6ff955fda].















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.karasiq.nanoboard.captcha.storage

import akka.util.ByteString
import com.karasiq.nanoboard.captcha.NanoboardCaptcha
import com.karasiq.nanoboard.captcha.internal.Constants

import scala.concurrent.Future

/**
  * Nanoboard captcha memory storage
  * @param data Encoded captcha blocks
  */
final class NanoboardCaptchaMemorySource(data: ByteString) extends NanoboardCaptchaSource {
  override def apply(index: Int): Future[NanoboardCaptcha] = {
    assert(index < this.length, s"Invalid index: $index, captcha blocks: $length")
    val offset: Int = index * Constants.BLOCK_LENGTH
    val block: ByteString = data.slice(offset, offset + Constants.BLOCK_LENGTH)
    assert(block.length == Constants.BLOCK_LENGTH, "Invalid captcha block")
    Future.successful(NanoboardCaptcha.fromBytes(block))
  }

  override val length: Int = data.length / Constants.BLOCK_LENGTH
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/captcha/storage/NanoboardCaptchaSource.scala version [c1559cf000].



























































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.karasiq.nanoboard.captcha.storage

import java.io.{ByteArrayOutputStream, InputStream}

import akka.util.ByteString
import com.karasiq.nanoboard.captcha.NanoboardCaptcha
import org.apache.commons.io.IOUtils

import scala.concurrent.Future

/**
  * Nanoboard captcha source
  */
trait NanoboardCaptchaSource extends IndexedSeq[Future[NanoboardCaptcha]] {
  /**
    * Extracts the captcha block with specified index
    * @param index Captcha block index
    * @return Captcha block
    */
  def apply(index: Int): Future[NanoboardCaptcha]

  /**
    * Captcha blocks count
    */
  def length: Int
}

object NanoboardCaptchaSource {
  /**
    * Opens nanoboard captcha file storage
    * @param file File path
    * @return Nanoboard captcha file storage
    */
  def fromFile(file: String) = {
    new NanoboardCaptchaFileSource(file)
  }

  /**
    * Opens encoded nanoboard captcha storage
    * @param data Captcha storage data
    * @return Nanoboard captcha memory storage
    */
  def fromBytes(data: ByteString) = {
    new NanoboardCaptchaMemorySource(data)
  }

  /**
    * Reads input stream into memory and decodes as captcha storage
    * @param inputStream Input stream
    * @return Nanoboard captcha memory storage
    */
  def fromInputStream(inputStream: InputStream) = {
    val outputStream = new ByteArrayOutputStream()
    try {
      IOUtils.copyLarge(inputStream, outputStream)
      fromBytes(ByteString(outputStream.toByteArray))
    } finally {
      IOUtils.closeQuietly(outputStream)
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/encoding/DataEncodingStage.scala version [9857d4840d].























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.karasiq.nanoboard.encoding

import akka.util.ByteString

import scala.language.implicitConversions

object DataEncodingStage {
  /**
    * Creates sequential data encoder, which is applied one after another in encoding, and backwards in decoding.
    */
  implicit def stageSeqToStage(seq: Seq[DataEncodingStage]): DataEncodingStage = new DataEncodingStage {
    override def encode(data: ByteString): ByteString = {
      seq.foldLeft(data)((data, stage) ⇒ stage.encode(data))
    }

    override def decode(data: ByteString): ByteString = {
      seq.foldRight(data)((stage, data) ⇒ stage.decode(data))
    }

    override def toString: String = {
      s"Sequential(${seq.mkString(", ")})"
    }
  }
}

/**
  * Generic data encoding stage
  */
trait DataEncodingStage {
  /**
    * Encodes data
    * @param data Source data
    * @return Encoded data
    */
  def encode(data: ByteString): ByteString

  /**
    * Decodes encoded data
    * @param data Previously encoded data
    * @return Source data
    */
  def decode(data: ByteString): ByteString
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/encoding/NanoboardCrypto.scala version [1530f93813].



















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.karasiq.nanoboard.encoding

import akka.util.ByteString
import org.bouncycastle.crypto.digests.{SHA256Digest, SHA512Digest}
import org.bouncycastle.crypto.{Digest, StreamCipher}
import org.bouncycastle.jce.provider.BouncyCastleProvider

/**
  * Internal cryptography utility
  */
private[nanoboard] object NanoboardCrypto {
  val provider = new BouncyCastleProvider

  @inline
  def sha256 = new SHA256Digest()

  @inline
  def sha512 = new SHA512Digest()

  implicit class BCDigestOps[T <: Digest](md: T) {
    def digest(data: ByteString): ByteString = {
      md.update(data.toArray, 0, data.length)
      val hash = Array.ofDim[Byte](md.getDigestSize)
      md.doFinal(hash, 0)
      ByteString(hash)
    }

    def updated(data: ByteString): T = {
      md.update(data.toArray, 0, data.length)
      md
    }
  }

  implicit class BCStreamCipherOps[T <: StreamCipher](cipher: T) {
    def process(data: ByteString): ByteString = {
      val buffer = Array.ofDim[Byte](data.length)
      val length = cipher.processBytes(data.toArray, 0, data.length, buffer, 0)
      ByteString(buffer).take(length)
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/encoding/NanoboardEncoding.scala version [3121a18770].





















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.karasiq.nanoboard.encoding

import com.karasiq.nanoboard.encoding.DataEncodingStage._
import com.karasiq.nanoboard.encoding.stages.{GzipCompression, PngEncoding, SalsaCipher}
import com.typesafe.config.{Config, ConfigFactory}

object NanoboardEncoding {
  /**
    * Creates default nanoboard data encoder with specified config
    * @param config Configuration object
    * @param pngEncoding PNG encoder
    * @return Default nanoboard data encoder
    * @see [[https://github.com/nanoboard/nanoboard/wiki/%D0%9D%D0%B0%D0%BD%D0%BE%D0%B1%D0%BE%D1%80%D0%B4%D0%B0 Original specification (Russian)]]
    */
  def fromConfig(config: Config, pngEncoding: PngEncoding = PngEncoding.default): DataEncodingStage = {
    Seq(GzipCompression(), SalsaCipher.fromConfig(config), pngEncoding)
  }

  /**
    * Same as [[com.karasiq.nanoboard.encoding.NanoboardEncoding#fromConfig(com.typesafe.config.Config, com.karasiq.nanoboard.encoding.stages.PngEncoding) fromConfig]],
    * but uses root configuration.
    */
  def apply(config: Config = ConfigFactory.load(), pngEncoding: PngEncoding = PngEncoding.default): DataEncodingStage = {
    fromConfig(config.getConfig("nanoboard"), pngEncoding)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/encoding/formats/CBORMessagePackFormat.scala version [b43c56c078].































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.karasiq.nanoboard.encoding.formats

import akka.util.ByteString
import com.karasiq.nanoboard.NanoboardMessage
import com.upokecenter.cbor.CBORObject

import scala.collection.JavaConversions._

/**
  * CBOR message pack format
  * @see [[http://cbor.io Concise Binary Object Representation]]
  */
trait CBORMessagePackFormat extends MessagePackFormat {
  /**
    * Parses messages from serialized data
    * @param payload Serialized messages
    * @return Parsed messages
    */
  override def parseMessages(payload: ByteString): Vector[NanoboardMessage] = {
    val messages = CBORObject.DecodeFromBytes(payload.toArray).get("messages")
    messages.getValues.toVector.map { message ⇒
      NanoboardMessage(message.get("parent").AsString(), message.get("text").AsString(), ByteString(message.get("pow").GetByteString()), ByteString(message.get("sign").GetByteString()))
    }
  }

  /**
    * Serializes messages to byte string
    * @param messages Messages
    * @return Serialized messages
    */
  override def writeMessages(messages: Seq[NanoboardMessage]): ByteString = {
    val messagesArray = messages.foldLeft(CBORObject.NewArray()) {
      case (array, NanoboardMessage(parent, text, pow, signature)) ⇒
        array.Add(
          CBORObject.NewMap()
            .Add("parent", parent)
            .Add("text", text)
            .Add("pow", pow.toArray[Byte])
            .Add("sign", signature.toArray[Byte])
        )
    }
    val payload = CBORObject.NewMap().Add("messages", messagesArray)
    ByteString(payload.EncodeToBytes())
  }
}

object CBORMessagePackFormat extends CBORMessagePackFormat

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/encoding/formats/MessagePackFormat.scala version [0bb6d85aff].



































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.karasiq.nanoboard.encoding.formats

import akka.util.ByteString
import com.typesafe.config.Config

import com.karasiq.nanoboard.NanoboardMessage

/**
  * Nanoboard message pack format
  */
trait MessagePackFormat {
  /**
    * Parses messages from serialized data
    * @param payload Serialized messages
    * @return Parsed messages
    */
  def parseMessages(payload: ByteString): Vector[NanoboardMessage]

  /**
    * Serializes messages to byte string
    * @param messages Messages
    * @return Serialized messages
    */
  def writeMessages(messages: Seq[NanoboardMessage]): ByteString
}

object MessagePackFormat {
  def apply(config: Config): MessagePackFormat = config.getString("nanoboard.message-pack-format").toLowerCase match {
    case "text" ⇒ TextMessagePackFormat
    case "cbor" ⇒ CBORMessagePackFormat
    case format ⇒ throw new IllegalArgumentException(s"Invalid format: $format")
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/encoding/formats/TextMessagePackFormat.scala version [bfe1772cc7].





































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.karasiq.nanoboard.encoding.formats

import scala.annotation.tailrec

import akka.util.ByteString

import com.karasiq.nanoboard.NanoboardMessage

/**
  * Legacy nanoboard message pack format.
  * Output starts with six-digits hex numbers, the first of which is a count of messages,
  * and subsequent are lengths of the messages itself, and followed by concatenated messages in format `replyTo` + `text`.
  * @see [[https://github.com/nanoboard/nanoboard/blob/master/Database/NanoPostPackUtil.cs Original implementation]]
  */
trait TextMessagePackFormat extends MessagePackFormat {
  final def parseMessages(payloadBs: ByteString): Vector[NanoboardMessage] = {
    val payload = payloadBs.utf8String
    val sizes: Vector[Int] = {
      val sizes: Iterator[Int] = payload.grouped(6)
        .map(bs ⇒ Integer.parseInt(bs, 16))

      val count = if (sizes.nonEmpty) sizes.next() else 0
      sizes.take(count).toVector
    }

    @tailrec
    def parse(str: String, sizes: Vector[Int], messages: Vector[NanoboardMessage] = Vector.empty): Vector[NanoboardMessage] = {
      if (sizes.isEmpty) {
        messages
      } else {
        val (data, rest) = str.splitAt(sizes.head)
        val (hash, rawMessage) = data.splitAt(NanoboardMessage.HashLength)
        val (message, pow, signature) = NanoboardMessage.stripSignatureTags(rawMessage)
        parse(rest, sizes.tail, messages :+ NanoboardMessage(hash, message, pow.getOrElse(NanoboardMessage.NoPOW), signature.getOrElse(NanoboardMessage.NoSignature)))
      }
    }

    val data = payload.drop((sizes.length + 1) * 6)
    assert(sizes.forall(_ > NanoboardMessage.HashLength) && sizes.sum <= data.length, "Invalid message sizes")
    parse(data, sizes)
  }

  final def writeMessages(messages: Seq[NanoboardMessage]): ByteString = {
    val payloads = messages.map(m ⇒ m.parent + NanoboardMessage.textWithSignatureTags(m))
    val sizes = Vector(payloads.length) ++ payloads.map(_.length)
    ByteString((sizes.map(size ⇒ f"$size%06x") ++ payloads).mkString)
  }
}

object TextMessagePackFormat extends TextMessagePackFormat

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/encoding/stages/GzipCompression.scala version [939a5e3b44].































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.karasiq.nanoboard.encoding.stages

import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
import java.util.zip.{GZIPInputStream, GZIPOutputStream}

import akka.util.ByteString
import com.karasiq.nanoboard.encoding.DataEncodingStage

/**
  * GZIP compression data stage.
  */
object GzipCompression extends GzipCompression {
  def apply(): GzipCompression = this
}

sealed class GzipCompression extends DataEncodingStage {
  /**
    * Compresses data with GZIP
    * @param data Source data
    * @return Compressed data
    */
  override def encode(data: ByteString): ByteString = {
    val inputStream = new ByteArrayInputStream(data.toArray)
    val byteArrayOutputStream = new ByteArrayOutputStream()
    val outputStream = new GZIPOutputStream(byteArrayOutputStream, 1024, true)
    try {
      val buffer = new Array[Byte](1024)
      var len = 0
      while (len != -1) {
        len = inputStream.read(buffer)
        if (len > 0) outputStream.write(buffer, 0, len)
      }
      outputStream.finish()
      outputStream.flush()
      ByteString(byteArrayOutputStream.toByteArray)
    } finally {
      inputStream.close()
      outputStream.close()
    }
  }

  /**
    * Decompresses GZIP-compressed data
    * @param data Previously compressed data
    * @return Source data
    */
  override def decode(data: ByteString): ByteString = {
    val inputStream = new GZIPInputStream(new ByteArrayInputStream(data.toArray), 1024)
    val outputStream = new ByteArrayOutputStream()
    try {
      val buffer = new Array[Byte](1024)
      var len = 0
      while (len != -1) {
        len = inputStream.read(buffer)
        if (len > 0) outputStream.write(buffer, 0, len)
      }
      ByteString(outputStream.toByteArray)
    } finally {
      inputStream.close()
      outputStream.close()
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/encoding/stages/PngEncoding.scala version [adb7b66685].































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package com.karasiq.nanoboard.encoding.stages

import java.awt.image.BufferedImage
import java.nio.ByteOrder
import java.util

import akka.util.ByteString
import com.karasiq.nanoboard.encoding.DataEncodingStage
import com.karasiq.nanoboard.utils._

object PngEncoding {
  /**
    * Creates PNG encoder
    * @param sourceImage Source image provider
    * @return PNG encoder
    */
  def apply(sourceImage: ByteString ⇒ BufferedImage): PngEncoding = {
    new PngEncoding(sourceImage)
  }

  /**
    * Creates PNG encoder, that uses single image for encoding
    * @param imageData Encoded image, must be readable with [[javax.imageio.ImageIO#read(java.io.InputStream) Java ImageIO]]
    * @return PNG encoder
    */
  def fromEncodedImage(imageData: ByteString): PngEncoding = {
    apply(_ ⇒ BufferedImages.fromBytes(imageData))
  }

  /**
    * Creates PNG encoder, that always generates random image for encoding
    * @return PNG encoder
    */
  def fromRandomImage(): PngEncoding = {
    apply { data ⇒
      val size = math.ceil(math.sqrt(requiredImageBytes(data) / 3)).toInt
      BufferedImages.generateImage(size, size)
    }
  }

  /**
    * Default PNG encoder
    */
  val default = fromRandomImage()

  /**
    * Image bytes, required to encode provided data
    * @param data Payload
    * @return Required image length
    */
  def requiredImageBytes(data: ByteString): Int = {
    (4 + data.length) * 8
  }

  /**
    * Available image size
    * @param image Source image
    * @return Actual image length
    */
  def imageBytes(image: BufferedImage): Int = {
    image.getWidth * image.getHeight * 3
  }
}

/**
  * PNG steganography encoding stage. Hides provided data in image color bits.
  * @param sourceImage Source image provider
  * @see [[http://blog.andersen.im/2014/11/hiding-your-bits-in-the-bytes/ Original algorithm]]
  */
final class PngEncoding(sourceImage: ByteString ⇒ BufferedImage) extends DataEncodingStage {
  private implicit val byteOrder = ByteOrder.LITTLE_ENDIAN

  @inline
  private def asInt(bytes: ByteString): Int = {
    bytes.padTo(4, 0.toByte).toByteBuffer.order(byteOrder).getInt
  }

  @inline
  private def asBytes(int: Int): ByteString = {
    ByteString.newBuilder
      .putInt(int)
      .result()
  }

  /**
    * Decodes hidden data from RGB array
    * @param bytes RGB array
    * @param dataLength Data length
    * @param index Data offset
    * @return Extracted data
    */
  private def readBytes(bytes: Array[Int], dataLength: Int, index: Int): ByteString = {
    val bitCount = dataLength * 8
    val offset = index * 8
    val required: Int = offset + bitCount - 1
    assert(bytes.length >= required, s"Invalid data length, $required bytes required")
    val result = new util.BitSet(bitCount)
    for (i ← 0 until bitCount) {
      result.set(i, bytes(offset + i) % 2 == 1)
    }
    ByteString(result.toByteArray).take(dataLength)
  }

  /**
    * Encodes provided data to RGB bytes
    * @param bytes RGB array
    * @param data Payload
    * @param byteIndex Data offset
    */
  private def writeBytes(bytes: Array[Int], data: ByteString, byteIndex: Int): Unit = {
    val bitOffset = byteIndex * 8
    val bitSet = util.BitSet.valueOf(data.toArray)
    val bitCount = data.length * 8
    for (i ← 0 until bitCount) {
      val index: Int = bitOffset + i
      val evenByte = bytes(index) - (bytes(index) % 2)
      bytes(index) = evenByte + (if (bitSet.get(i)) 1 else 0)
    }
  }

  override def encode(data: ByteString): ByteString = {
    // Request source image for provided data
    val img = sourceImage(data)
    assert(img.ne(null), "Container image not found")

    // Decode RGB data
    val bytes: Array[Int] = img.toRgbArray
    val requiredSize: Int = PngEncoding.requiredImageBytes(data)
    assert(bytes.length >= requiredSize, s"Image is too small, $requiredSize bits required")

    // Write length
    writeBytes(bytes, asBytes(data.length), 0)

    // Write payload
    writeBytes(bytes, data, 4)

    // Convert bytes to RGB data
    val rgb = BufferedImages.toColorArray(bytes)
    img.setRGB(0, 0, img.getWidth, img.getHeight, rgb, 0, img.getWidth)

    // Render image as PNG
    img.toBytes("png")
  }

  override def decode(data: ByteString): ByteString = {
    // Decode image
    val img = BufferedImages.fromBytes(data)
    assert(img.ne(null), "Invalid image")

    // Decode RGB data
    val bytes: Array[Int] = img.toRgbArray

    // Read data length
    val length: Int = asInt(readBytes(bytes, 4, 0))

    // Read payload
    readBytes(bytes, length, 4)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/encoding/stages/SalsaCipher.scala version [39c60bdf75].





























































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.karasiq.nanoboard.encoding.stages

import akka.util.ByteString
import com.karasiq.nanoboard.encoding.DataEncodingStage
import com.karasiq.nanoboard.encoding.NanoboardCrypto.{BCDigestOps, BCStreamCipherOps, sha256}
import com.typesafe.config.Config
import org.bouncycastle.crypto.engines.Salsa20Engine
import org.bouncycastle.crypto.params.{KeyParameter, ParametersWithIV}

object SalsaCipher {
  /**
    * Creates Salsa20 cipher stage from specified config
    * @param nbConfig Configuration object
    * @return Salsa20 cipher stage
    */
  def fromConfig(nbConfig: Config): SalsaCipher = {
    apply(nbConfig.getString("encryption-key"))
  }

  /**
    * Creates Salsa20 cipher stage with the specified key
    * @param key Encryption key
    * @return Salsa20 cipher stage
    */
  def apply(key: String): SalsaCipher = {
    new SalsaCipher(key)
  }
}

/**
  * Salsa20 stream cipher encryption stage
  * @param key Encryption key
  */
final class SalsaCipher(key: String) extends DataEncodingStage {
  private def createCipher(encryption: Boolean): Salsa20Engine = {
    val cipher = new Salsa20Engine()

    val keyBytes = ByteString(key)
    val secretKey = new KeyParameter(sha256.digest(keyBytes).toArray, 0, 32)
    val iv: Array[Byte] = sha256.digest(keyBytes.reverse).toArray
    cipher.init(encryption, new ParametersWithIV(secretKey, iv, 0, 8))
    cipher
  }

  /**
    * Encrypts provided data with the Salsa20 stream cipher
    * @param data Source data
    * @return Encrypted data
    */
  override def encode(data: ByteString): ByteString = {
    createCipher(encryption = true).process(data)
  }

  /**
    * Decrypts data, encrypted with the Salsa20 stream cipher
    * @param data Encrypted data
    * @return Source data
    */
  override def decode(data: ByteString): ByteString = {
    createCipher(encryption = false).process(data)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/sources/bitmessage/BitMessageTransport.scala version [7c2fa28a0f].

















































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.karasiq.nanoboard.sources.bitmessage

import java.nio.charset.StandardCharsets

import scala.concurrent.Future

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.stream.{ActorMaterializer, OverflowStrategy}
import akka.stream.scaladsl.{Sink, Source}
import com.typesafe.config.{Config, ConfigFactory}
import org.apache.commons.codec.binary.Base64
import play.api.libs.json._

import com.karasiq.nanoboard.NanoboardMessage

object BitMessageTransport {
  def fromConfig(bmConfig: Config)(implicit ac: ActorSystem, am: ActorMaterializer) = {
    val chanAddress = bmConfig.getString("chan-address")
    val apiAddress = bmConfig.getString("host")
    val apiPort = bmConfig.getInt("port")
    val apiUsername = bmConfig.getString("username")
    val apiPassword = bmConfig.getString("password")
    new BitMessageTransport(chanAddress, apiAddress, apiPort, apiUsername, apiPassword)
  }

  def apply(config: Config = ConfigFactory.load())(implicit ac: ActorSystem, am: ActorMaterializer) = {
    fromConfig(config.getConfig("nanoboard.bitmessage"))
  }

  def wrap(messages: NanoboardMessage*): String = {
    val wrappedMessages = messages.map(m ⇒ WrappedNanoboardMessage(m.hash, asBase64(NanoboardMessage.textWithSignatureTags(m)), m.parent))
    Json.toJson(wrappedMessages).toString()
  }

  def unwrap(bitMessage: String): Vector[NanoboardMessage] = {
    val messages = Json.parse(bitMessage).as[Vector[WrappedNanoboardMessage]]
    messages.map { wrapped ⇒
      val (text, pow, signature) = NanoboardMessage.stripSignatureTags(fromBase64(wrapped.message))
      NanoboardMessage(wrapped.replyTo, text, pow.getOrElse(NanoboardMessage.NoPOW), signature.getOrElse(NanoboardMessage.NoSignature))
    }
  }

  @inline
  private[bitmessage] def asBase64(string: String): String = {
    Base64.encodeBase64String(string.getBytes(StandardCharsets.UTF_8))
  }

  @inline
  private[bitmessage] def fromBase64(string: String): String = {
    new String(Base64.decodeBase64(string), StandardCharsets.UTF_8)
  }
}

/**
  * Nanoboard BitMessage transport, compatible with official implementation.
  * @see [[https://github.com/nanoboard/nanoboard-bittransport]]
  */
final class BitMessageTransport(chanAddress: String, apiAddress: String, apiPort: Int, apiUsername: String, apiPassword: String)(implicit ac: ActorSystem, am: ActorMaterializer) {
  import XmlRpcProxy._
  private val http = Http()
  private val xmlRpcProxy = new XmlRpcProxy(http, apiAddress, apiPort, apiUsername, apiPassword)

  def sendMessage(message: NanoboardMessage): Future[HttpResponse] = {
    xmlRpcProxy.sendMessage(chanAddress, chanAddress, (), BitMessageTransport.asBase64(BitMessageTransport.wrap(message)), 2, 21600)
  }

  def receiveMessages(host: String, port: Int, sink: Sink[NanoboardMessage, _]): Future[Http.ServerBinding] = {
    http.bindAndHandle(route(sink), host, port)
  }

  private def route(sink: Sink[NanoboardMessage, _]) = {
    val queue = Source
      .queue(20, OverflowStrategy.dropHead)
      .to(sink)
      .run()

    post {
      (path("api" / "add" / NanoboardMessage.HashFormat) & entity(as[String])) { (parent, message) ⇒
        val (text, pow, signature) = NanoboardMessage.stripSignatureTags(BitMessageTransport.fromBase64(message))
        queue.offer(NanoboardMessage(parent, text, pow.getOrElse(NanoboardMessage.NoPOW), signature.getOrElse(NanoboardMessage.NoSignature)))
        complete(StatusCodes.OK)
      }
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/sources/bitmessage/WrappedNanoboardMessage.scala version [967b210438].





























>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.karasiq.nanoboard.sources.bitmessage

import play.api.libs.json.Json

import com.karasiq.nanoboard.NanoboardMessage

@SerialVersionUID(0L)
private[bitmessage] final case class WrappedNanoboardMessage(hash: String, message: String, replyTo: String) {
  assert(hash.length == NanoboardMessage.HashLength && replyTo.length == NanoboardMessage.HashLength, "Invalid hashes")
}

private[bitmessage] object WrappedNanoboardMessage {
  implicit val wrappedNanoboardMessageFormat = Json.format[WrappedNanoboardMessage]
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/sources/bitmessage/XmlRpcProxy.scala version [1fc8f7a139].

































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.karasiq.nanoboard.sources.bitmessage

import scala.concurrent.Future
import scala.language.{dynamics, implicitConversions}

import akka.http.scaladsl.HttpExt
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
import akka.stream.ActorMaterializer
import scalatags.Text.all._
import scalatags.text.Builder

import com.karasiq.nanoboard.sources.bitmessage.XmlRpcProxy.{XmlRpcParameter, XmlRpcTags}

/**
  * Simple XML-RPC wrapper, based on `akka-http`
  */
private[bitmessage] final class XmlRpcProxy(http: HttpExt, apiAddress: String, apiPort: Int, apiUsername: String, apiPassword: String)(implicit am: ActorMaterializer) extends Dynamic {
  def applyDynamic(method: String)(args: XmlRpcParameter*): Future[HttpResponse] = {
    import XmlRpcTags._
    val entity = "<?xml version=\"1.0\"?>" + methodCall(
      methodName(method),
      params(
        for (arg ← args) yield param(value(arg))
      )
    )
    val authentication = Authorization(BasicHttpCredentials(apiUsername, apiPassword))
    val url = s"http://$apiAddress:$apiPort/"
    http.singleRequest(HttpRequest(method = HttpMethods.POST, uri = url, entity = HttpEntity(ContentTypes.`text/xml(UTF-8)`, entity), headers = List(authentication)))
  }
}

private[bitmessage] object XmlRpcProxy {
  object XmlRpcTags {
    val methodCall = tag("methodCall")
    val methodName = tag("methodName")
    val params = tag("params")
    val param = tag("param")
    val value = tag("value")
    val int = tag("int")
  }

  sealed trait XmlDataWrapper[T] {
    def toModifier(value: T): Modifier
  }

  implicit object StringXmlDataWrapper extends XmlDataWrapper[String] {
    def toModifier(value: String) = value
  }

  implicit object IntXmlDataWrapper extends XmlDataWrapper[Int] {
    def toModifier(value: Int) = XmlRpcTags.int(value)
  }

  implicit object UnitXmlDataWrapper extends XmlDataWrapper[Unit] {
    def toModifier(value: Unit) = ()
  }

  sealed trait XmlRpcParameter extends Modifier

  implicit def anyToXmlRpcParameter[T: XmlDataWrapper](value: T): XmlRpcParameter = new XmlRpcParameter {
    def applyTo(t: Builder) = implicitly[XmlDataWrapper[T]].toModifier(value).applyTo(t)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/sources/png/BoardPngSource.scala version [919194d3eb].





























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.karasiq.nanoboard.sources.png

import java.net.{InetSocketAddress, URL}

import scala.collection.JavaConversions._
import scala.util.Try

import akka.actor.ActorSystem
import akka.http.scaladsl.{ClientTransport, Http}
import akka.http.scaladsl.model.HttpRequest
import akka.http.scaladsl.settings.ConnectionPoolSettings
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Source
import akka.util.ByteString
import com.typesafe.config.Config
import org.jsoup.Jsoup
import org.jsoup.nodes.{Document, Element}

import com.karasiq.nanoboard.NanoboardMessage
import com.karasiq.nanoboard.encoding.DataEncodingStage

private object BoardPngSource {
  def createHttpSettings(config: Config) = {
    val settings = ConnectionPoolSettings(config)
    val proxy = Try(config.getString("nanoboard.proxy")).toOption
    proxy match {
      case Some(proxy) if proxy.contains(":") ⇒
        val Array(host, port) = proxy.split(":", 2)
        settings.withTransport(ClientTransport.httpsProxy(InetSocketAddress.createUnresolved(host, port.toInt)))

      case Some(proxy) if proxy.nonEmpty ⇒
        settings.withTransport(ClientTransport.httpsProxy(InetSocketAddress.createUnresolved(proxy, 8080)))

      case _ ⇒
        settings
    }
  }
}

/**
  * Generic imageboard PNG downloader
  * @param encoding PNG data decoder
  */
class BoardPngSource(encoding: DataEncodingStage)(implicit as: ActorSystem, am: ActorMaterializer) extends UrlPngSource {
  protected final val http = Http()
  protected val httpSettings = BoardPngSource.createHttpSettings(as.settings.config)

  def messagesFromImage(url: String): Source[NanoboardMessage, akka.NotUsed] = {
    Source.fromFuture(http.singleRequest(HttpRequest(uri = url), settings = httpSettings))
      .flatMapConcat(_.entity.dataBytes.fold(ByteString.empty)(_ ++ _))
      .mapConcat { data ⇒
        // println(encoding.decode(data).utf8String)
        NanoboardMessage.parseMessages(encoding.decode(data))
      }
      .recoverWithRetries(1, { case _ ⇒ Source.empty })
      .named("boardImageMessages")
  }

  def imagesFromPage(url: String): Source[String, akka.NotUsed] = {
    Source.fromFuture(http.singleRequest(HttpRequest(uri = url), settings = httpSettings))
      .flatMapConcat(_.entity.dataBytes.fold(ByteString.empty)(_ ++ _))
      .flatMapConcat(data ⇒ imagesFromPage(Jsoup.parse(data.utf8String, url)))
      .recoverWithRetries(1, { case _ ⇒ Source.empty })
      .named("boardImages")
  }

  protected def imagesFromPage(page: Document): Source[String, akka.NotUsed] = {
    val urls = page.select("a").flatMap(getUrl(_, "href"))
    Source(urls.distinct.toVector)
  }

  protected def getUrl(e: Element, attr: String): Option[String] = {
    Try(new URL(e.absUrl(attr)))
      .toOption
      .filter(_.getPath.toLowerCase.endsWith(".png")) // .filter(_.getPath.matches("([^\\?\\s]+)?/src/([^\\?\\s]+)?\\.png"))
      .map(_.toString)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/sources/png/UrlPngSource.scala version [85eeb16a75].





























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.karasiq.nanoboard.sources.png

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Source
import com.typesafe.config.Config

import com.karasiq.nanoboard.NanoboardMessage
import com.karasiq.nanoboard.encoding.{DataEncodingStage, NanoboardEncoding}

/**
  * PNG downloader interface
  */
trait UrlPngSource {
  /**
    * Downloads and parses the messages from provided URL
    * @param url PNG image URL
    * @return Nanoboard messages
    */
  def messagesFromImage(url: String): Source[NanoboardMessage, akka.NotUsed]

  /**
    * Downloads and provides list of available PNG images from the page
    * @param url Page URL
    * @return PNG images URL
    */
  def imagesFromPage(url: String): Source[String, akka.NotUsed]
}

object UrlPngSource {
  def fromConfig(config: Config)(implicit as: ActorSystem, am: ActorMaterializer): UrlPngSource = {
    apply(NanoboardEncoding.fromConfig(config))
  }

  def apply(config: Config)(implicit as: ActorSystem, am: ActorMaterializer): UrlPngSource = {
    apply(NanoboardEncoding(config))
  }

  def apply(encoding: DataEncodingStage)(implicit as: ActorSystem, am: ActorMaterializer): UrlPngSource = {
    new BoardPngSource(encoding)
  }

  def apply()(implicit as: ActorSystem, am: ActorMaterializer): UrlPngSource = {
    apply(as.settings.config)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/main/scala/com/karasiq/nanoboard/utils/package.scala version [8b2cb024c7].























































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package com.karasiq.nanoboard

import java.awt.Color
import java.awt.image.BufferedImage
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
import javax.imageio.ImageIO

import akka.util.ByteString
import org.apache.commons.codec.binary.Hex
import org.apache.commons.io.IOUtils

import scala.util.Random

package object utils {
  private[nanoboard] implicit class ByteStringCompanionOps(private val bs: ByteString.type) extends AnyVal {
    /**
      * Creates byte string from hex string (each byte represented as two chars from 00 to FF)
      * @param string Hex string
      * @return Byte string
      * @see [[org.apache.commons.codec.binary.Hex#decodeHex(char[])]]
      */
    def fromHexString(string: String): ByteString = {
      bs.apply(Hex.decodeHex(string.toCharArray))
    }
  }

  private[nanoboard] implicit class ByteStringOps(private val bs: ByteString) extends AnyVal {
    /**
      * Encodes byte string as hex string (each byte represented as two chars from 00 to FF)
      * @param lowerCase Use lower-case hex chars
      * @return Hex string
      * @see [[org.apache.commons.codec.binary.Hex#encodeHex(byte[], boolean)]]
      */
    def toHexString(lowerCase: Boolean = true): String = {
      String.valueOf(Hex.encodeHex(bs.toArray, lowerCase))
    }
  }

  private[nanoboard] object BufferedImages {
    /**
      * Convers color array to RGB array
      * Input format: {{{Array(rgb)}}}
      * Output format: {{{Array(red, green, blue)}}}
      * @param colors Color array
      * @return RGB array
      */
    def toRgbArray(colors: Array[Int]): Array[Int] = {
      val bytes = new Array[Int](colors.length * 3)
      for (i ← colors.indices) {
        val color = new Color(colors(i))
        bytes(i * 3) = color.getRed
        bytes(i * 3 + 1) = color.getGreen
        bytes(i * 3 + 2) = color.getBlue
      }
      bytes
    }

    /**
      * Converts RGB array to color array, which can be used in [[java.awt.image.BufferedImage#setRGB(int, int, int, int, int[], int, int) setRGB function]].
      * Input format: {{{Array(red, green, blue)}}}
      * Output format: {{{Array(rgb)}}}
      * @param arr RGB array
      * @return Color array
      */
    def toColorArray(arr: Array[Int]): Array[Int] = {
      val result = new Array[Int](arr.length / 3)
      for (i ← result.indices) {
        result(i) = new Color(arr(i * 3), arr(i * 3 + 1), arr(i * 3 + 2)).getRGB
      }
      result
    }

    /**
      * Generates random image
      * @param width Image width
      * @param height Image height
      * @return Generated image
      */
    def generateImage(width: Int, height: Int): BufferedImage = {
      val image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
      val rgb = Array.fill[Int](width * height)(Random.nextInt())
      image.setRGB(0, 0, width, height, rgb, 0, width)
      image
    }

    /**
      * Reads image from bytes
      * @param data Encoded image
      * @return Decoded image
      * @see [[javax.imageio.ImageIO#read(java.io.InputStream)]]
      */
    def fromBytes(data: ByteString): BufferedImage = {
      val inputStream = new ByteArrayInputStream(data.toArray)
      try {
        ImageIO.read(inputStream)
      } finally {
        IOUtils.closeQuietly(inputStream)
      }
    }
  }

  private[nanoboard] implicit class BufferedImageOps(private val img: BufferedImage) extends AnyVal {
    /**
      * Encodes image to color array
      * Output format: {{{Array(rgb)}}}
      * @return Color array
      */
    def toColorArray: Array[Int] = {
      val colors = new Array[Int](img.getWidth * img.getHeight)
      img.getRGB(0, 0, img.getWidth, img.getHeight, colors, 0, img.getWidth)
      colors
    }

    /**
      * Decodes image to RGB array.
      * Output format: {{{Array(red, green, blue)}}}
      * @return RGB array
      */
    def toRgbArray: Array[Int] = {
      BufferedImages.toRgbArray(toColorArray)
    }

    /**
      * Encodes image to bytes
      * @param format Image encoding format
      * @return Encoded image
      * @see [[javax.imageio.ImageIO#write(java.awt.image.RenderedImage, java.lang.String, java.io.OutputStream)]]
      */
    def toBytes(format: String): ByteString = {
      val outputStream = new ByteArrayOutputStream()
      try {
        ImageIO.write(img, format, outputStream)
        ByteString(outputStream.toByteArray)
      } finally {
        IOUtils.closeQuietly(outputStream)
      }
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/test/resources/reference.conf version [7db183f0c0].







>
>
>
1
2
3
nanoboard {
  encryption-key = "nano3"
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/test/resources/test-captcha.nbc version [13ebde12a4].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/test/resources/test-encoded.png version [6b896136f5].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/test/scala/com/karasiq/nanoboard/test/BitMessageTest.scala version [cd2bf02a62].







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.karasiq.nanoboard.test

import org.scalatest.{FlatSpec, Matchers}

import com.karasiq.nanoboard.NanoboardMessage
import com.karasiq.nanoboard.sources.bitmessage.BitMessageTransport

class BitMessageTest extends FlatSpec with Matchers {
  "BitMessage transport" should "encode message" in {
    val message = NanoboardMessage("e062de33e103343281ececdd645f8632", "Test")
    val wrapped = BitMessageTransport.wrap(message)
    wrapped shouldBe """[{"hash":"95a8c5c2ddec08a2eac00a24281bf5a2","message":"VGVzdA==","replyTo":"e062de33e103343281ececdd645f8632"}]"""
    BitMessageTransport.unwrap(wrapped) shouldBe Vector(message)
  }

  it should "decode message" in {
    BitMessageTransport.unwrap("""[{"hash":"90b90b6b88eeaf0660f260bd6bedcdcf","message":"W2ddVHVlLCA4L01hci8yMDE2LCAwMjozNjo1My45OTkgKEV1cm9wZS9Nb3Njb3cpLCBjbGllbnQ6IGthcmFzaXEtbmFub2JvYXJkIHYxLjAuM1svZ10K0KLQtdGB0YIg0LLQvdC10YjQvdC10Lkg0LrQsNGA0YLQuNC90LrQuApbc2ltZz1odHRwOi8vZnM1LmRpcmVjdHVwbG9hZC5uZXQvaW1hZ2VzLzE2MDMwOC95aWhwOTNzbC5wbmdd","replyTo":"f1fbb838193bb358c9d00fdfcfa028fe"}]""") shouldBe Vector(NanoboardMessage("f1fbb838193bb358c9d00fdfcfa028fe", "[g]Tue, 8/Mar/2016, 02:36:53.999 (Europe/Moscow), client: karasiq-nanoboard v1.0.3[/g]\nТест внешней картинки\n[simg=http://fs5.directupload.net/images/160308/yihp93sl.png]"))
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/test/scala/com/karasiq/nanoboard/test/CaptchaTest.scala version [f79c3f78ce].















































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.karasiq.nanoboard.test

import java.nio.file.{Files, Paths}

import scala.concurrent.Await
import scala.concurrent.duration._

import akka.util.ByteString
import org.scalatest.{FlatSpec, Matchers}

import com.karasiq.nanoboard.NanoboardMessage
import com.karasiq.nanoboard.captcha.{NanoboardCaptcha, NanoboardPow}
import com.karasiq.nanoboard.captcha.impl.NanoboardPowV2
import com.karasiq.nanoboard.captcha.storage.NanoboardCaptchaSource
import com.karasiq.nanoboard.encoding.NanoboardCrypto._
import com.karasiq.nanoboard.test.utils.TestFiles
import com.karasiq.nanoboard.utils._

class CaptchaTest extends FlatSpec with Matchers {
  implicit val executionContext = NanoboardPow.executionContext()

  val post = NanoboardMessage("0" * 32, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
  val preCalculatedPow = ByteString.fromHexString("40ecb63c4168ceed2d018d0f756b8f07e66d4a31054db005766b56ad3f745c06e56fcf7f3030deb1d264cdba0af0b332ef11f83c951ed42a166f8d24efdcd010196b3acdeed8e6527b4fb45bcd15ef09ad16541322423d609019d76c2534680b3f3ab918caedfb45727548c3462ca29efe58a5c7f192bcb43995c071a907099e")
  val powCalculator = new NanoboardPowV2(3, 3, 1)
  
  "Captcha file" should "be parsed" in {
    val captchaFileName = TestFiles.unpackResource("test-captcha.nbc")
    val captchaFile = NanoboardCaptchaSource.fromFile(captchaFileName)
    try {
      val signedPost = post.copy(pow = preCalculatedPow)
      val signPayload = powCalculator.getSignPayload(signedPost)
      val captcha = Await.result(captchaFile(powCalculator.getCaptchaIndex(signedPost, captchaFile.length)), Duration.Inf)

      val captchaPng = NanoboardCaptcha.render(captcha, 50, 20)
      // CaptchaTest.saveToFile(captchaPng, "test-captcha.png")

      val validSignature = captcha.signature(signPayload, "sovyo")
      captcha.verify(signPayload, validSignature) shouldBe true

      val invalidSignature = captcha.signature(signPayload, "11111")
      captcha.verify(signPayload, invalidSignature) shouldBe false

      validSignature.toHexString() shouldBe "fcba6fa8b4d853a676dfa2033868276785ca2ed160333525fe1f506d4e1c60df5e52ad0f1d4c949e4bea14fddfc0c347ca4b16bbe2a87e55a57681d4fe04a303"
      sha256.digest(captchaPng).toHexString() shouldBe "7573466048bb005b5f04f3a198907b62d748c434230bd537afca1d39e135b5cc"
      sha256.digest(captcha.image).toHexString() shouldBe "55b95e7a9af5dcf2f48f0f417c59a72748fa8db7b141bdd78580443d9a64c413"
      captcha.publicKey.toHexString() shouldBe "7aed3dc8d009c749970356b64ddb445068fec933293a5cf56ff65a36d97e8079"
      captcha.seed.toHexString() shouldBe "fad5a58c217e0c6985e3241dd275bf667c47d3773b904e24cf0a39707741f815"

      Await.result(NanoboardCaptcha.verify(post.copy(pow = preCalculatedPow, signature = validSignature), powCalculator, captchaFile)(scala.concurrent.ExecutionContext.global), Duration.Inf) shouldBe true
    } finally {
      captchaFile.close()
      Files.delete(Paths.get(captchaFileName))
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/test/scala/com/karasiq/nanoboard/test/FileEncodingTest.scala version [52f49a54ce].





















































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.karasiq.nanoboard.test

import scala.util.Random

import akka.util.ByteString
import org.scalatest.{FlatSpec, Matchers}

import com.karasiq.nanoboard.NanoboardMessage
import com.karasiq.nanoboard.encoding.DataEncodingStage
import com.karasiq.nanoboard.encoding.DataEncodingStage._
import com.karasiq.nanoboard.encoding.formats.{CBORMessagePackFormat, TextMessagePackFormat}
import com.karasiq.nanoboard.encoding.stages.{GzipCompression, PngEncoding, SalsaCipher}
import com.karasiq.nanoboard.test.utils.TestFiles

//noinspection ScalaDeprecation
class FileEncodingTest extends FlatSpec with Matchers {
  val testImage = TestFiles.resource("test-encoded.png")
  val testMessages = Vector(NanoboardMessage("0" * 32, "Test message 1"), NanoboardMessage("1" * 32, "Test message 2"))
  val pngEncoding = PngEncoding.fromRandomImage()
  val gzipCompression = GzipCompression()
  val salsaCipher = SalsaCipher("nano")

  val testData = {
    val array = new Array[Byte](50000)
    Random.nextBytes(array)
    ByteString(array)
  }

  "PNG encoder" should "encode data to png image" in {
    val encoded = pngEncoding.encode(testData)
    val decoded = pngEncoding.decode(encoded)
    decoded shouldBe testData
  }

  it should "compress and encrypt data" in {
    val stages = Seq[DataEncodingStage](
      gzipCompression,
      salsaCipher,
      Seq(gzipCompression, salsaCipher)
    )

    stages.foreach { stage ⇒
      println(s"Testing stage: $stage")
      val encoded = stage.encode(testData)
      val decoded = stage.decode(encoded)
      decoded shouldBe testData
    }
  }

  it should "decode ciphered data" in {
    val stage = Seq(gzipCompression, salsaCipher, pngEncoding)
    val decoded = stage.decode(testImage)
    decoded.length shouldBe 279352
    val encoded = stage.encode(decoded)
    assert(stage.decode(encoded) == decoded && decoded.utf8String.startsWith("0000270000aa000083006cc0000259003f3a0050ea0062150091cd000083003f8b0000800001170005ca00014500136000ea370000360000d8003b3c0034fd0000880000350032d500008c0000cb00007f002e4600005e00008e0000b70000d00000780000a100005e00008b00003700007100005e00226c9953dabbec38c625670087e8be5eca66[g]01/Mar/2016, 01:15:26 (UTC), client: nboard v1.7.13[/g]"))

    NanoboardMessage.parseMessages(decoded) // .foreach(println)
  }

  "Message pack" should "be encoded in text format" in {
    val result = TextMessagePackFormat.writeMessages(testMessages)
    TextMessagePackFormat.parseMessages(result) shouldBe testMessages
    result.hashCode() shouldBe 2029017934
    // println(result.utf8String)
    // TestFiles.saveToFile(result, "text-test.txt")
  }

  it should "be encoded in CBOR format" in {
    val result = CBORMessagePackFormat.writeMessages(testMessages)
    CBORMessagePackFormat.parseMessages(result) shouldBe testMessages
    result.hashCode() shouldBe -1960199224
    // TestFiles.saveToFile(result, "cbor-test.bin")
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/test/scala/com/karasiq/nanoboard/test/NanoboardPowV1Test.scala version [002052a513].

cannot compute difference between binary files

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/test/scala/com/karasiq/nanoboard/test/NanoboardPowV2Test.scala version [ae1ba27044].





























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.karasiq.nanoboard.test

import scala.concurrent.Await
import scala.concurrent.duration.Duration

import akka.util.ByteString
import org.scalatest.{FlatSpec, Matchers}

import com.karasiq.nanoboard.NanoboardMessage
import com.karasiq.nanoboard.captcha.NanoboardPow
import com.karasiq.nanoboard.captcha.impl.NanoboardPowV2
import com.karasiq.nanoboard.utils._

class NanoboardPowV2Test extends FlatSpec with Matchers {
  val post = NanoboardMessage("0" * 32, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
  val preCalculatedPow = ByteString.fromHexString("40ecb63c4168ceed2d018d0f756b8f07e66d4a31054db005766b56ad3f745c06e56fcf7f3030deb1d264cdba0af0b332ef11f83c951ed42a166f8d24efdcd010196b3acdeed8e6527b4fb45bcd15ef09ad16541322423d609019d76c2534680b3f3ab918caedfb45727548c3462ca29efe58a5c7f192bcb43995c071a907099e")
  val powCalculator = new NanoboardPowV2(3, 3, 1)(NanoboardPow.executionContext())

  "POW calculator" should "verify hash" in {
    val signedPost = post.copy(pow = preCalculatedPow)
    powCalculator.verify(post) shouldBe false
    powCalculator.verify(signedPost) shouldBe true
    powCalculator.getCaptchaIndex(signedPost, 18000) shouldBe 14104
  }

  it should "calculate valid hash" in {
    val powResult = Await.result(powCalculator.calculate(post), Duration.Inf)
    powCalculator.verify(post.copy(pow = powResult)) shouldBe true
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/test/scala/com/karasiq/nanoboard/test/SourceTest.scala version [ec9b2f2c99].









































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.karasiq.nanoboard.test

import scala.concurrent.Await
import scala.concurrent.duration._

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Sink
import org.scalatest.{BeforeAndAfterAll, FlatSpec, Matchers}
import org.scalatest.tags.Network

import com.karasiq.nanoboard.sources.png.UrlPngSource

@Network
class SourceTest extends FlatSpec with Matchers with BeforeAndAfterAll {
  implicit val actorSystem = ActorSystem("source-test")
  implicit val actorMaterializer = ActorMaterializer()
  val source = UrlPngSource()

  "Source loader" should "load messages from image" in {
    val imageSource = source.messagesFromImage("https://endchan.xyz/.media/78db64519e62d9edce7b6a8dbb47fd17-imagepng.png")
    val messages = Await.result(imageSource.runWith(Sink.seq), Duration.Inf)
    messages.length shouldBe 29
  }

  it should "load messages from thread" in {
    val imageSource = source.imagesFromPage("https://endchan.xyz/test/res/971.html").flatMapConcat(source.messagesFromImage)
    val messages = Await.result(imageSource.take(100).runWith(Sink.seq), Duration.Inf)
    messages.length shouldBe 100
  }

  override protected def afterAll(): Unit = {
    actorSystem.terminate()
    super.afterAll()
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/library/src/test/scala/com/karasiq/nanoboard/test/utils/TestFiles.scala version [a8e2c1fdf8].





















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.karasiq.nanoboard.test.utils

import java.io.{ByteArrayInputStream, FileOutputStream}
import java.nio.file.Files

import akka.util.ByteString
import org.apache.commons.io.IOUtils

object TestFiles {
  def resource(name: String): ByteString = {
    val input = getClass.getClassLoader.getResourceAsStream("test-encoded.png")
    try {
      ByteString(IOUtils.toByteArray(input))
    } finally {
      IOUtils.closeQuietly(input)
    }
  }

  def unpackResource(name: String): String = {
    val fileName = Files.createTempFile("captcha", ".nbc").toString
    val input = getClass.getClassLoader.getResourceAsStream(name)
    val output = new FileOutputStream(fileName)
    try {
      IOUtils.copyLarge(input, output)
      fileName
    } finally {
      IOUtils.closeQuietly(input)
      IOUtils.closeQuietly(output)
    }
  }

  def saveToFile(data: ByteString, fileName: String): Unit = {
    val input = new ByteArrayInputStream(data.toArray)
    val output = new FileOutputStream(fileName)
    try {
      IOUtils.copyLarge(input, output)
    } finally {
      IOUtils.closeQuietly(input)
      IOUtils.closeQuietly(output)
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/project/NanoboardAssets.scala version [600e9124a8].





























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import scalatags.Text.all._

object NanoboardAssets {
  def index = {
    "<!DOCTYPE html>" + html(
      head(
        base(href := "/"),
        meta(name := "viewport", content := "width=device-width, initial-scale=1.0"),
        link(rel := "shortcut icon", href := "favicon.ico", `type` := "image/x-icon")
      ),
      body(
        // Empty
      )
    )
  }

  def style = {
    """
      |td.buttons {
      |    text-align: center;
      |}
      |
      |.panel-primary .panel-head-buttons .glyphicon {
      |    color: white;
      |}
      |
      |.glyphicon {
      |    margin-left: 2px;
      |    margin-right: 2px;
      |}
      |
      |.panel-title .glyphicon {
      |    margin-right: 10px;
      |}
    """.stripMargin
  }

  def highlightJsLanguages = Vector(
    "bash", "clojure", "coffeescript", "cpp", "cs", "d", "delphi", "erlang", "fsharp",
    "go", "groovy", "haskell", "java", "javascript", "json", "lua", "lisp", "markdown",
    "objectivec", "perl", "php", "python", "ruby", "rust", "scala", "scheme", "sql",
    "swift", "typescript", "css", "xml"
  )

  def highlightJsStyle = "monokai-sublime"
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/project/build.properties version [d94a10d4f3].



>
1
sbt.version = 0.13.16

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/project/plugins.sbt version [e2cc5727de].































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
logLevel := Level.Warn

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.1")

addSbtPlugin("com.github.karasiq" % "sbt-scalajs-bundler" % "1.0.7")

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.22")

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2")

libraryDependencies ++= Seq(
  "com.lihaoyi" %% "scalatags" % "0.5.4",
  "org.webjars.bower" % "marked" % "0.3.5",
  "org.webjars" % "highlightjs" % "9.2.0"
)

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/setup/categories.txt version [bbc89d9a5f].













>
>
>
>
>
>
1
2
3
4
5
6
cd94a3d60f2f521806abebcd3dc3f549
Бред/Разное
355c600bef54376b89fd62ff4d636daf
Обсуждение Наноборды
6aebf50d682b475265b24636fe2054e2
18+

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/setup/launch4j.xml version [5dbdf2e995].

































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<launch4jConfig>
  <dontWrapJar>false</dontWrapJar>
  <headerType>console</headerType>
  <jar>..\target\universal\stage\lib\nanoboard-server.jar</jar>
  <outfile>..\target\universal\nanoboard.exe</outfile>
  <errTitle></errTitle>
  <cmdLine></cmdLine>
  <chdir>.</chdir>
  <priority>normal</priority>
  <downloadUrl>http://java.com/download</downloadUrl>
  <supportUrl></supportUrl>
  <stayAlive>false</stayAlive>
  <restartOnCrash>false</restartOnCrash>
  <manifest></manifest>
  <icon>..\frontend\files\favicon.ico</icon>
  <singleInstance>
    <mutexName>nanoboard-server</mutexName>
    <windowTitle></windowTitle>
  </singleInstance>
  <jre>
    <path>jre1.8.0_102</path>
    <bundledJre64Bit>true</bundledJre64Bit>
    <bundledJreAsFallback>true</bundledJreAsFallback>
    <minVersion>1.8.0</minVersion>
    <maxVersion></maxVersion>
    <jdkPreference>preferJre</jdkPreference>
    <runtimeBits>64/32</runtimeBits>
    <initialHeapSize>128</initialHeapSize>
    <maxHeapSize>512</maxHeapSize>
  </jre>
</launch4jConfig>

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/setup/places.txt version [64aeac6687].





































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
http://dobrochan.com/mad/res/75979.xhtml
http://volgach.ru/b/res/5226.html
http://02ch.su/b/res/7379.html
http://chaos.cyberpunk.us/st/50
http://endchan.xyz/test/res/971.html
http://xynta.ch/b/res/10540.html
http://www.nowere.net/wa/res/6271.html
http://alphachan.org/art/res/329789.html
http://hamstakilla.com/b/22279
https://2ch.hk/srv/res/1930.html
https://2ch.hk/crypt/
http://cn.urbanculture.in/bb/res/16.html
https://0chan.pl/_frct/res/1.html
https://2ch.hk/obr/res/488.html
https://2ch.hk/cute/res/5790.html
https://2ch.hk/mlpr/res/92518.html
https://volgach.ru/b/res/5226.html
https://2-ch.su/b/res/553021.html
http://syn-ch.ru/b/res/3916184.html
http://gaika.ch/w/res/206.html
https://0chan.ru.net/b/res/10023531.html
https://02ch.su/b/res/7379.html
https://2ch.hk/m/res/3730.html
http://0chan.ru.net/b/res/10023531.html
https://2ch.hk/who/res/1394.html
https://2ch.hk/wp/res/56561.html
https://2ch.hk/8/res/2570.html
https://2ch.hk/test/res/6599.html
https://hamstakilla.com/b/22279
https://www.nowere.net/wa/res/6271.html
https://xynta.ch/b/res/10540.html
https://endchan.xyz/test/res/971.html
https://lolifox.org/b/res/2581.html
https://www.0nyan.space/b/res/20743.html

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/setup/setup.iss version [bad97634a4].





























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#define OutputName "nanoboard-server"
#define MyAppName "Nanoboard"
#define MyAppVersion "1.3.2"
#define MyAppPublisher "Karasiq, Inc."
#define MyAppURL "http://www.github.com/Karasiq/nanoboard"
#define MyAppExeName "nanoboard.exe"
#define ProjectFolder "..\"

[Setup]
AppId={{4e1629c6-a53d-48ee-80b8-bd6644e62a1f}}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName}
OutputDir={#ProjectFolder}\target\iss
OutputBaseFilename={#OutputName}-{#MyAppVersion}
SetupIconFile={#ProjectFolder}\frontend\files\favicon.ico
Compression=lzma
SolidCompression=true
PrivilegesRequired=admin

[Languages]
Name: english; MessagesFile: compiler:Default.isl
Name: russian; MessagesFile: compiler:Languages\Russian.isl

[Tasks]
Name: desktopicon; Description: {cm:CreateDesktopIcon}; GroupDescription: {cm:AdditionalIcons}; Languages: 

[Files]
Source: {#ProjectFolder}\target\universal\nanoboard.exe; DestDir: {app}; Flags: ignoreversion
Source: c:\Program Files\Java\jre1.8.0_102\*; DestDir: {app}\jre1.8.0_102; Flags: recursesubdirs ignoreversion
Source: {#ProjectFolder}\setup\places.txt; DestDir: {app}; Flags: ignoreversion
Source: {#ProjectFolder}\setup\categories.txt; DestDir: {app}; Flags: ignoreversion
Source: {#ProjectFolder}\frontend\files\favicon.ico; DestDir: {app}; Flags: ignoreversion

[Icons]
Name: {group}\{#MyAppName}; Filename: {app}\{#MyAppExeName}; IconFilename: {app}\favicon.ico; WorkingDir: {app}
Name: {commondesktop}\{#MyAppName}; Filename: {app}\{#MyAppExeName}; Tasks: desktopicon; IconFilename: {app}\favicon.ico; WorkingDir: {app}

[Run]
Filename: {app}\{#MyAppExeName}; Description: {cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}; Flags: shellexec postinstall skipifsilent; WorkingDir: {app}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/shared/shared/src/main/scala/com/karasiq/nanoboard/api/NanoboardCaptchaRequest.scala version [a1a989c664].















>
>
>
>
>
>
>
1
2
3
4
5
6
7
package com.karasiq.nanoboard.api

case class NanoboardCaptchaImage(index: Int, image: Array[Byte])

case class NanoboardCaptchaRequest(postHash: String, pow: Array[Byte], captcha: NanoboardCaptchaImage)

case class NanoboardCaptchaAnswer(request: NanoboardCaptchaRequest, answer: String)

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/shared/shared/src/main/scala/com/karasiq/nanoboard/api/NanoboardCategory.scala version [0c9cbc9ffe].







>
>
>
1
2
3
package com.karasiq.nanoboard.api

case class NanoboardCategory(hash: String, name: String)

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/shared/shared/src/main/scala/com/karasiq/nanoboard/api/NanoboardContainer.scala version [d721a7bd0a].







>
>
>
1
2
3
package com.karasiq.nanoboard.api

case class NanoboardContainer(id: Long, url: String, time: Long, posts: Int)

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/shared/shared/src/main/scala/com/karasiq/nanoboard/api/NanoboardMessageData.scala version [8e404c426f].

































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.karasiq.nanoboard.api

object NanoboardMessageData {
  val NO_POW = Array.fill[Byte](128)(0)
  val NO_SIGNATURE = Array.fill[Byte](64)(0)
}

case class NanoboardMessageData(containerId: Option[Long], parent: Option[String], hash: String, text: String, answers: Int, pow: Array[Byte] = NanoboardMessageData.NO_POW, signature: Array[Byte] = NanoboardMessageData.NO_SIGNATURE) {
  def isSigned: Boolean = {
    !signature.sameElements(NanoboardMessageData.NO_SIGNATURE)
  }

  def isCategory: Boolean = {
    parent.isEmpty
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/shared/shared/src/main/scala/com/karasiq/nanoboard/api/NanoboardReply.scala version [b728c94483].







>
>
>
1
2
3
package com.karasiq.nanoboard.api

case class NanoboardReply(parent: String, message: String)

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/shared/shared/src/main/scala/com/karasiq/nanoboard/streaming/NanoboardEvent.scala version [98445fdd53].









































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.karasiq.nanoboard.streaming

import boopickle.CompositePickler
import boopickle.Default._
import com.karasiq.nanoboard.api.NanoboardMessageData

sealed trait NanoboardEvent

object NanoboardEvent {
  case class PostAdded(post: NanoboardMessageData) extends NanoboardEvent
  case class PostDeleted(hash: String) extends NanoboardEvent
  case class PostVerified(post: NanoboardMessageData) extends NanoboardEvent

  implicit val eventPickler: CompositePickler[NanoboardEvent] = compositePickler[NanoboardEvent]

  eventPickler
    .addConcreteType[PostAdded]
    .addConcreteType[PostDeleted]
    .addConcreteType[PostVerified]
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/shared/shared/src/main/scala/com/karasiq/nanoboard/streaming/NanoboardEventSeq.scala version [e73b1bcc90].



















>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
package com.karasiq.nanoboard.streaming

import boopickle.Default._

final case class NanoboardEventSeq(events: Seq[NanoboardEvent])

object NanoboardEventSeq {
  implicit val nanoboardEventSeqFormat = generatePickler[NanoboardEventSeq]
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/shared/shared/src/main/scala/com/karasiq/nanoboard/streaming/NanoboardSubscription.scala version [44af7c33ca].





























>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.karasiq.nanoboard.streaming

import boopickle.Default._

sealed trait NanoboardSubscription
object NanoboardSubscription {
  case object Unfiltered extends NanoboardSubscription
  case class PostHashes(hashes: Set[String]) extends NanoboardSubscription

  implicit val subscriptionPickler = compositePickler[NanoboardSubscription]

  subscriptionPickler.addConcreteType[PostHashes]
  subscriptionPickler.addConcreteType[Unfiltered.type]
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/resources/application.conf version [f57eab8893].































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
akka.http {
  server {
    server-header = nanoboard/${nanoboard.version}
    request-timeout = 3600s
    idle-timeout = 24h
    socket-options {
      tcp-no-delay = true
    }
  }

  client {
    parsing.max-content-length = 1g
    user-agent-header = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.52 Safari/537.36 OPR/31.0.1889.50 (Edition beta)"
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/resources/reference.conf version [977734d5e7].



















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
nanoboard {
  external-config-file = nanoboard.conf
  max-post-size = 200k
  pow-required = true

  database {
    url = "jdbc:h2:file:"${user.home}/.nanoboard/index_v12
    driver = org.h2.Driver
    connectionPool = disabled
    keepAliveConnection = true
  }

  scheduler {
    update-interval = 15m
    posts-per-container = 100
    spam-filter = [
      // "(?i)^[a-z0-9+/=]+$"
    ]
  }

  server {
    host = 127.0.0.1
    port = 7347
  }

  bitmessage {
    receive = true
    send = true
    listen-host = 127.0.0.1
    listen-port = 7346
    host = 127.0.0.1
    port = 8442
    username = "nanoapi"
    password = "nano"
  }

  captcha {
    download-url = "https://github.com/Karasiq/nanoboard/releases/download/v1.2.0/ffeaeb19.nbc"
    storage = ${user.home}/.nanoboard
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/dispatcher/NanoboardDispatcher.scala version [32955f870f].





























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.karasiq.nanoboard.dispatcher

import akka.util.ByteString
import com.karasiq.nanoboard.api.{NanoboardCaptchaRequest, NanoboardContainer, NanoboardMessageData}
import com.karasiq.nanoboard.{NanoboardCategory, NanoboardMessage}

import scala.concurrent.Future

trait NanoboardDispatcher {
  def createContainer(pending: Int, random: Int, format: String, container: ByteString): Future[ByteString]
  def recent(offset: Long, count: Long): Future[Seq[NanoboardMessageData]]
  def pending(offset: Long, count: Long): Future[Seq[NanoboardMessageData]]
  def places(): Future[Seq[String]]
  def categories(): Future[Seq[NanoboardMessageData]]
  def post(hash: String): Future[Option[NanoboardMessageData]]
  def thread(hash: String, offset: Long, count: Long): Future[Seq[NanoboardMessageData]]
  def addPost(source: String, message: NanoboardMessage): Future[Int]
  def reply(parent: String, text: String): Future[NanoboardMessageData]
  def markAsNotPending(message: String): Future[Unit]
  def markAsPending(message: String): Future[Unit]
  def delete(hash: String): Future[Seq[String]]
  def delete(offset: Long, count: Long): Future[Seq[String]]
  def clearDeleted(): Future[Int]
  def updatePlaces(places: Seq[String]): Future[Unit]
  def updateCategories(categories: Seq[NanoboardCategory]): Future[Unit]
  def containers(offset: Long, count: Long): Future[Seq[NanoboardContainer]]
  def clearContainer(id: Long): Future[Seq[String]]
  def requestVerification(hash: String): Future[NanoboardCaptchaRequest]
  def verifyPost(request: NanoboardCaptchaRequest, answer: String): Future[NanoboardMessageData]
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/dispatcher/NanoboardSlickDispatcher.scala version [c9c64709d1].

































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package com.karasiq.nanoboard.dispatcher

import scala.concurrent.{ExecutionContext, Future}

import akka.actor.ActorSystem
import akka.stream.{ActorMaterializer, OverflowStrategy}
import akka.stream.scaladsl.{Sink, Source}
import akka.util.ByteString
import com.typesafe.config.{Config, ConfigFactory}
import slick.driver.H2Driver.api._

import com.karasiq.nanoboard.{NanoboardCategory, NanoboardMessage, NanoboardMessageGenerator}
import com.karasiq.nanoboard.api.{NanoboardCaptchaImage, NanoboardCaptchaRequest, NanoboardContainer, NanoboardMessageData}
import com.karasiq.nanoboard.captcha.{NanoboardCaptcha, NanoboardPow}
import com.karasiq.nanoboard.captcha.storage.NanoboardCaptchaSource
import com.karasiq.nanoboard.encoding.NanoboardEncoding
import com.karasiq.nanoboard.encoding.formats.MessagePackFormat
import com.karasiq.nanoboard.encoding.stages.PngEncoding
import com.karasiq.nanoboard.model.{categories ⇒ categoriesTable, _}
import com.karasiq.nanoboard.model.MessageConversions._
import com.karasiq.nanoboard.streaming.NanoboardEvent

object NanoboardSlickDispatcher {
  def apply(db: Database, captcha: NanoboardCaptchaSource, config: Config = ConfigFactory.load(), eventSink: Sink[NanoboardEvent, _] = Sink.ignore)
           (implicit ec: ExecutionContext, as: ActorSystem, am: ActorMaterializer): NanoboardDispatcher = {
    new NanoboardSlickDispatcher(db, captcha, config, eventSink)
  }
}

private[dispatcher] final class NanoboardSlickDispatcher(db: Database, captcha: NanoboardCaptchaSource, config: Config, eventSink: Sink[NanoboardEvent, _])
                                                        (implicit ec: ExecutionContext, as: ActorSystem, am: ActorMaterializer) extends NanoboardDispatcher {

  private[this] val powContext = NanoboardPow.executionContext()
  private[this] val powCalculator = NanoboardPow(config)(powContext)
  private[this] val messageGenerator = NanoboardMessageGenerator(config)
  private[this] val messageFormat = MessagePackFormat(config)

  private[this] val eventQueue = Source.queue(20, OverflowStrategy.dropHead)
    .to(eventSink)
    .run()

  override def createContainer(pendingCount: Int, randomCount: Int, format: String, container: ByteString) = {
    val pending = Post.pending(0, pendingCount)
    val rand = SimpleFunction.nullary[Double]("rand")
    val random = for (ps ← posts.sortBy(_ ⇒ rand).take(randomCount).result) yield ps.map(MessageConversions.wrapDbPost(_, 0))
    val query = for (p ← pending; r ← random) yield (p ++ r).toVector

    val stage = NanoboardEncoding(config, if (container.nonEmpty) PngEncoding.fromEncodedImage(container) else PngEncoding.default)
    val future = db.run(query).map { posts ⇒
      val data = messageFormat.writeMessages(posts.map(MessageConversions.unwrapToMessage))
      val encoded = stage.encode(data)
      // assert(stage.decode(encoded) == data, "Container is broken")
      posts.map(_.hash) → encoded
    }

    future.flatMap {
      case (hashes, result) ⇒
        db.run(for (_ ← pendingPosts.filter(_.hash inSet hashes).delete) yield result)
    }
  }

  override def recent(offset: Long, count: Long): Future[Seq[NanoboardMessageData]] = {
    db.run(Post.recent(offset, count))
  }

  override def pending(offset: Long, count: Long): Future[Seq[NanoboardMessageData]] = {
    db.run(Post.pending(offset, count))
  }

  override def places(): Future[Seq[String]] = {
    db.run(Place.list())
  }

  override def categories(): Future[Seq[NanoboardMessageData]] = {
    db.run(Category.list())
  }

  override def post(hash: String): Future[Option[NanoboardMessageData]] = {
    db.run(Post.get(hash))
  }

  override def thread(hash: String, offset: Long, count: Long): Future[Seq[NanoboardMessageData]] = {
    db.run(Post.thread(hash, offset, count))
  }

  override def markAsNotPending(message: String): Future[Unit] = {
    db.run(DBIO.seq(pendingPosts.filter(_.hash === message).delete))
  }

  override def markAsPending(message: String): Future[Unit] = {
    db.run(DBIO.seq(pendingPosts.insertOrUpdate(message)))
  }

  override def delete(hash: String): Future[Seq[String]] = {
    val future = db.run(Post.deleteCascade(hash))
    future.foreach { deleted ⇒
      deleted.foreach(hash ⇒ eventQueue.offer(NanoboardEvent.PostDeleted(hash)))
    }
    future
  }

  override def delete(offset: Long, count: Long): Future[Seq[String]] = {
    val query = for {
      ps ← posts.sortBy(_.firstSeen.desc).drop(offset).take(count).result
      deleted ← DBIO.sequence(ps.map(p ⇒ Post.delete(p.hash)))
    } yield deleted

    val future = db.run(query)
    future.foreach { deleted ⇒
      deleted.foreach(hash ⇒ eventQueue.offer(NanoboardEvent.PostDeleted(hash)))
    }
    future
  }

  override def clearDeleted(): Future[Int] = {
    db.run(deletedPosts.delete)
  }

  override def addPost(source: String, message: NanoboardMessage): Future[Int] = {
    val query = for {
      container ← Container.forUrl(source)
      inserted ← Post.insertMessage(container, message)
    } yield (container, inserted)

    val future = db.run(query)
    future.foreach {
      case (container, inserted) ⇒
        if (inserted > 0) eventQueue.offer(NanoboardEvent.PostAdded(wrapMessage(message, Some(container))))
    }
    future.map(_._2)
  }

  override def reply(parent: String, text: String): Future[NanoboardMessageData] = {
    val newMessage: NanoboardMessage = messageGenerator.newMessage(parent, text)
    val future = db.run(Post.addReply(newMessage))
    future.foreach { msg ⇒
      eventQueue.offer(NanoboardEvent.PostAdded(msg))
    }
    future
  }

  override def updatePlaces(places: Seq[String]): Future[Unit] = {
    db.run(Place.update(places))
  }

  override def updateCategories(categories: Seq[NanoboardCategory]): Future[Unit] = {
    db.run(Category.update(categories))
  }

  override def containers(offset: Long, count: Long): Future[Seq[NanoboardContainer]] = {
    db.run(Container.list(offset, count))
  }

  override def clearContainer(id: Long): Future[Seq[String]] = {
    db.run(Container.clearPosts(id))
  }

  def requestVerification(hash: String): Future[NanoboardCaptchaRequest] = {
    val query = for {
      (parent, text) ← posts.filter(_.hash === hash).map(p ⇒ (p.parent, p.text)).result.head
      powMessage = NanoboardMessage(parent, text)
      pow ← DBIO.from(powCalculator.calculate(powMessage))
      captchaId = powCalculator.getCaptchaIndex(powMessage.copy(pow = pow), captcha.length)
      captchaImage ← DBIO.from(captcha(captchaId))
    } yield NanoboardCaptchaRequest(hash, pow.toArray, NanoboardCaptchaImage(captchaId, NanoboardCaptcha.render(captchaImage).toArray))
    db.run(query)
  }

  def verifyPost(request: NanoboardCaptchaRequest, answer: String): Future[NanoboardMessageData] = {
    val query = for {
      DBPost(_, parent, text, firstSeen, containerId, _, _) ← posts.filter(_.hash === request.postHash).result.head
      pow ← DBIO.successful(ByteString(request.pow))
      unsigned = NanoboardMessage(parent, text, pow)
      signPayload = powCalculator.getSignPayload(unsigned)
      captchaId = powCalculator.getCaptchaIndex(unsigned, captcha.length)
      captcha ← DBIO.from(this.captcha(captchaId))
      signature = captcha.signature(signPayload, answer) if captcha.verify(signPayload, signature)
      newMessage = NanoboardMessage(parent, text, pow, signature)
      newPost ← DBIO.seq(
        posts.filter(_.hash === request.postHash).delete,
        posts += DBPost(newMessage.hash, newMessage.parent, newMessage.text, firstSeen, containerId, pow, signature),
        pendingPosts += newMessage.hash
      )
    } yield MessageConversions.wrapMessage(newMessage, Some(containerId))

    val future = db.run(query)
    future.foreach { message ⇒
      eventQueue.offer(NanoboardEvent.PostVerified(message))
    }
    future
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/model/ConfigQueries.scala version [b8bc744488].



























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.karasiq.nanoboard.model

import com.karasiq.nanoboard.NanoboardCategory
import slick.driver.H2Driver.api._

import scala.concurrent.ExecutionContext

trait ConfigQueries { self: Tables ⇒
  object Place {
    def list() = {
      places.result
    }

    def update(newList: Seq[String])(implicit ec: ExecutionContext) = DBIO.sequence(
      newList.map(url ⇒ places.insertOrUpdate(url)) :+
        places.filterNot(_.url inSet newList).delete
    ).map(_ ⇒ ())
  }

  object Category {
    def list()(implicit ec: ExecutionContext) = {
      val query = categories.map { c ⇒
        (c, posts.filter(_.parent === c.hash).length)
      }
      query.result.map(_.map {
        case (category, answers) ⇒
          MessageConversions.wrapCategory(category, answers)
      })
    }

    def update(newList: Seq[NanoboardCategory])(implicit ec: ExecutionContext) = DBIO.sequence(
      newList.map(c ⇒ add(c.hash, c.name)):+
        categories.filterNot(_.hash inSet newList.map(_.hash)).delete
    ).map(_ ⇒ ())

    def add(hash: String, name: String) = DBIO.seq(
      deletedPosts.filter(_.hash === hash).delete,
      categories.insertOrUpdate(NanoboardCategory(hash, name))
    )

    def delete(hash: String) = {
      categories.filter(_.hash === hash).delete
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/model/ContainerQueries.scala version [b204640e69].











































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.karasiq.nanoboard.model

import java.time.Instant

import com.karasiq.nanoboard.api.NanoboardContainer
import slick.driver.H2Driver.api._

import scala.concurrent.ExecutionContext

trait ContainerQueries { self: Tables with PostQueries ⇒
  object Container {
    def create(url: String)(implicit ec: ExecutionContext) = {
      containers.returning(containers.map(_.id)) += DBContainer(0, url, Instant.now().toEpochMilli)
    }

    def forUrl(url: String)(implicit ec: ExecutionContext) = {
      containers.filter(_.url === url).map(_.id).result.headOption.flatMap {
        case Some(id) ⇒
          DBIO.successful(id)

        case None ⇒
          create(url)
      }
    }

    private def listQuery(offset: ConstColumn[Long], count: ConstColumn[Long]) = {
      containers
        .map(c ⇒ (c, posts.filter(_.containerId === c.id).length))
        .filter(_._2 > 0)
        .sortBy(_._1.time.desc)
        .drop(offset)
        .take(count)
    }

    private val listCompiled = Compiled(listQuery _)

    def list(offset: Long, count: Long)(implicit ec: ExecutionContext) = {
      listCompiled(offset, count)
        .result
        .map(_.map {
          case (DBContainer(id, url, time), posts) ⇒
            NanoboardContainer(id, url, time, posts)
        })
    }

    def clearPosts(id: Long)(implicit ec: ExecutionContext) = {
      for {
        hashes ← posts.filter(_.containerId === id).map(_.hash).result
        deleted ← DBIO.sequence(hashes.map(Post.delete))
      } yield deleted
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/model/MessageConversions.scala version [f1e2336599].



















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.karasiq.nanoboard.model

import akka.util.ByteString
import com.karasiq.nanoboard.api.NanoboardMessageData
import com.karasiq.nanoboard.{NanoboardCategory, NanoboardMessage}

import scala.language.implicitConversions

private[nanoboard] object MessageConversions {
  def wrapMessage(message: NanoboardMessage, container: Option[Long] = None, answers: Int = 0): NanoboardMessageData = {
    NanoboardMessageData(container, Some(message.parent), message.hash, message.text, answers, message.pow.toArray, message.signature.toArray)
  }

  def wrapDbPost[P <: Tables#DBPost](dbPost: P, answers: Int = 0): NanoboardMessageData = {
    NanoboardMessageData(Some(dbPost.containerId), Some(dbPost.parent), dbPost.hash, dbPost.text, answers, dbPost.pow.toArray, dbPost.signature.toArray)
  }

  def wrapCategory(category: NanoboardCategory, answers: Int = 0): NanoboardMessageData = {
    NanoboardMessageData(None, None, category.hash, category.name, answers)
  }

  def unwrapToMessage(message: NanoboardMessageData): NanoboardMessage = {
    NanoboardMessage(message.parent.getOrElse(throw new IllegalArgumentException("Invalid parent hash")), message.text, ByteString(message.pow), ByteString(message.signature))
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/model/PostQueries.scala version [62a14cf449].



































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package com.karasiq.nanoboard.model

import java.time.{Instant, LocalDate}

import com.karasiq.nanoboard.NanoboardMessage
import slick.driver.H2Driver.api._
import slick.lifted.{Compiled, ConstColumn}

import scala.concurrent.ExecutionContext

trait PostQueries { self: Tables with ConfigQueries with ContainerQueries ⇒
  object Post {
    def addReply(m: NanoboardMessage)(implicit ec: ExecutionContext) = {
      for {
        container ← Container.forUrl(s"local://${LocalDate.now()}")
        _ ← DBIO.seq(insertMessage(container, m), /* pendingPosts.forceInsertQuery {
          val exists = (for (p <- pendingPosts if p.hash === m.hash) yield ()).exists
          for (message <- Query(m.hash) if !exists) yield message
        }, */ deletedPosts.filter(_.hash === m.hash).delete)
      } yield MessageConversions.wrapMessage(m, Some(container))
    }

    def insertMessage(container: Long, m: NanoboardMessage) = posts.forceInsertQuery {
      val deleted = (for (p <- deletedPosts if p.hash inSet Seq(m.hash, m.parent)) yield ()).exists
      val exists = (for (p <- posts if p.hash === m.hash) yield ()).exists
      val insert = (m.hash, m.parent, m.text, Instant.now().toEpochMilli, container, m.pow, m.signature) <> (DBPost.tupled, DBPost.unapply)
      for (message <- Query(insert) if !deleted && !exists) yield message
    }

    def insertMessages(container: Long, messages: Seq[NanoboardMessage]) = {
      DBIO.sequence(messages.map(insertMessage(container, _)))
    }

    private def recentQuery(offset: ConstColumn[Long], count: ConstColumn[Long]) = {
      posts
        .sortBy(_.firstSeen.desc)
        .drop(offset)
        .take(count)
        .map(post ⇒ (post, posts.filter(_.parent === post.hash).length))
    }

    private val recentCompiled = Compiled(recentQuery _)

    def recent(offset: Long, count: Long)(implicit ec: ExecutionContext) = {
      for (ps ← recentCompiled(offset, count).result) yield ps.map {
        case (post, answers) ⇒
          MessageConversions.wrapDbPost(post, answers)
      }
    }

    private def pendingQuery(offset: ConstColumn[Long], count: ConstColumn[Long]) = {
      pendingPosts
        .flatMap(_.post)
        .sortBy(_.firstSeen.asc)
        .drop(offset)
        .take(count)
    }

    private val pendingCompiled = Compiled(pendingQuery _)

    def pending(offset: Long, count: Long)(implicit ec: ExecutionContext) = {
      for (ps ← pendingCompiled(offset, count).result) yield ps.map(MessageConversions.wrapDbPost(_, 0))
    }

    private def getQuery(hash: Rep[String]) = {
      posts
        .filter(_.hash === hash)
        .map(post ⇒ (post, posts.filter(_.parent === post.hash).length))
    }

    private val getCompiled = Compiled(getQuery _)

    def get(hash: String)(implicit ec: ExecutionContext) = {
      for (p ← getCompiled(hash).result.headOption) yield p.map {
        case (post, answers) ⇒
          MessageConversions.wrapDbPost(post, answers)
      }
    }

    private def threadQuery(hash: Rep[String], offset: ConstColumn[Long], count: ConstColumn[Long]) = {
      val query = posts.filter(_.parent === hash)
        .sortBy(_.firstSeen.desc)
        .drop(offset)
        .take(count)

      def withAnswerCount(query: Query[Post, DBPost, Seq]) = query.map { post ⇒
        (post, posts.filter(_.parent === post.hash).length)
      }

      withAnswerCount(query)
    }

    private val threadCompiled = Compiled(threadQuery _)

    def thread(hash: String, offset: Long, count: Long)(implicit ec: ExecutionContext) = {
      for {
        opPost ← get(hash)
        answers ← threadCompiled(hash, offset, count).result
      } yield opPost.toVector ++ answers.map {
        case (post, answers) ⇒
          MessageConversions.wrapDbPost(post, answers)
      }
    }

    def delete(hash: String)(implicit ec: ExecutionContext) = {
      for {
        _ ← DBIO.seq(
          pendingPosts.filter(_.hash === hash).delete,
          Category.delete(hash),
          deletedPosts.insertOrUpdate(hash),
          posts.filter(_.hash === hash).delete
        )
      } yield hash
    }

    def deleteCascade(hash: String)(implicit ec: ExecutionContext) = {
      def deleteCascadeRec(hash: String): DBIOAction[Seq[String], NoStream, Effect.Write with Effect.Read] = {
        val query = posts.filter(_.parent === hash)
        for {
          deleted ← query.map(_.hash).result
          descendants ← DBIO.sequence(deleted.map(deleteCascadeRec))
          _ ← delete(hash)
        } yield Vector(hash) ++ deleted ++ descendants.flatten
      }

      deleteCascadeRec(hash)
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/model/Tables.scala version [1a22defbc2].



























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.karasiq.nanoboard.model

import scala.language.postfixOps

import akka.util.ByteString
import slick.driver.H2Driver.api._
import slick.lifted.TableQuery

import com.karasiq.nanoboard.{NanoboardCategory, NanoboardMessage}

trait Tables {
  implicit val byteStringColumnType = MappedColumnType.base[ByteString, Array[Byte]](_.toArray[Byte], ByteString.apply)

  case class DBPost(hash: String, parent: String, text: String, firstSeen: Long, containerId: Long, pow: ByteString, signature: ByteString)

  // TODO: Descending recent index: https://github.com/slick/slick/issues/1035
  class Post(tag: Tag) extends Table[DBPost](tag, "posts") {
    def hash = column[String]("hash", O.SqlType(s"char(${NanoboardMessage.HashLength})"), O.PrimaryKey)
    def parent = column[String]("parent_hash", O.SqlType(s"char(${NanoboardMessage.HashLength})"))
    def text = column[String]("message")
    def firstSeen = column[Long]("first_seen")
    def containerId = column[Long]("container_id")
    def pow = column[ByteString]("pow_value", O.SqlType(s"binary(${NanoboardMessage.POWLength})"))
    def signature = column[ByteString]("signature", O.SqlType(s"binary(${NanoboardMessage.SignatureLength})"))

    def container = foreignKey("post_container", containerId, containers)(_.id, ForeignKeyAction.Restrict, ForeignKeyAction.Cascade)
    def threadIdx = index("thread_index", (parent, firstSeen), unique = false)
    def recentIdx = index("recent_index", firstSeen, unique = false)
    def containerIdx = index("container_index", containerId, unique = false)
    def * = (hash, parent, text, firstSeen, containerId, pow, signature) <> (DBPost.tupled, DBPost.unapply)
  }

  val posts = TableQuery[Post]

  class DeletedPost(tag: Tag) extends Table[String](tag, "posts_deleted") {
    def hash = column[String]("hash", O.SqlType(s"char(${NanoboardMessage.HashLength})"), O.PrimaryKey)
    def * = hash
  }

  val deletedPosts = TableQuery[DeletedPost]

  class PendingPost(tag: Tag) extends Table[String](tag, "pending_posts") {
    def hash = column[String]("hash", O.SqlType(s"char(${NanoboardMessage.HashLength})"), O.PrimaryKey)
    def post = foreignKey("post_fk", hash, posts)(_.hash, onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
    def * = hash
  }

  val pendingPosts = TableQuery[PendingPost]

  class Place(tag: Tag) extends Table[String](tag, "places") {
    def url = column[String]("place_url", O.PrimaryKey)
    def * = url
  }

  val places = TableQuery[Place]

  class Category(tag: Tag) extends Table[NanoboardCategory](tag, "categories") {
    def hash = column[String]("category_hash", O.SqlType(s"char(${NanoboardMessage.HashLength})"), O.PrimaryKey)
    def name = column[String]("category_name")
    def * = (hash, name) <> (NanoboardCategory.tupled, NanoboardCategory.unapply)
  }

  val categories = TableQuery[Category]

  case class DBContainer(id: Long, url: String, time: Long)
  class Container(tag: Tag) extends Table[DBContainer](tag, "containers") {
    def id = column[Long]("container_id", O.PrimaryKey, O.AutoInc)
    def url = column[String]("container_url")
    def time = column[Long]("container_time")

    def idx = index("container_url_index", url, unique = true)
    def timeIdx = index("container_time_index", time)
    def * = (id, url, time) <> (DBContainer.tupled, DBContainer.unapply)
  }

  val containers = TableQuery[Container]
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/model/package.scala version [3591da5758].











>
>
>
>
>
1
2
3
4
5
package com.karasiq.nanoboard

import scala.language.postfixOps

package object model extends Tables with ConfigQueries with PostQueries with ContainerQueries

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/server/BinaryMarshaller.scala version [7c73ac8e59].





































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.karasiq.nanoboard.server

import akka.http.scaladsl.marshalling._
import akka.http.scaladsl.model._
import akka.http.scaladsl.unmarshalling.Unmarshaller
import akka.util.ByteString
import boopickle.Default._

private[server] trait BinaryMarshaller {
  implicit def defaultMarshaller[T: Pickler]: ToEntityMarshaller[T] = {
    val contentType = ContentTypes.`application/octet-stream`
    Marshaller.withFixedContentType(contentType)((value: T) ⇒ HttpEntity(contentType, ByteString(Pickle.intoBytes(value))))
  }

  def defaultUnmarshaller[A, B](implicit ev: Pickler[B], m: Unmarshaller[A, ByteString]): Unmarshaller[A, B] = {
    m.map(bs ⇒ Unpickle[B].fromBytes(bs.toByteBuffer))
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/server/JsonMarshaller.scala version [5228081926].

































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.karasiq.nanoboard.server

import akka.http.scaladsl.marshalling._
import akka.http.scaladsl.model._
import akka.http.scaladsl.unmarshalling.Unmarshaller
import play.api.libs.json._

private[server] trait JsonMarshaller {
  implicit def defaultMarshaller[T: Writes]: ToEntityMarshaller[T] = {
    Marshaller.withFixedContentType(ContentTypes.`application/json`)((value: T) ⇒ HttpEntity(ContentTypes.`application/json`, Json.toJson(value).toString()))
  }

  def defaultUnmarshaller[A, B](implicit ev: Reads[B], m: Unmarshaller[A, String]): Unmarshaller[A, B] = {
    m.map(str ⇒ Json.parse(str).as[B])
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/server/Main.scala version [9de586f46a].





















































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package com.karasiq.nanoboard.server

import java.nio.file.{Files, Paths}
import java.time.LocalDate
import java.util.concurrent.TimeUnit

import akka.actor.ActorSystem
import akka.event.Logging
import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.ServerBinding
import akka.stream._
import akka.stream.scaladsl._
import com.karasiq.nanoboard.dispatcher.NanoboardSlickDispatcher
import com.karasiq.nanoboard.model.{Place, _}
import com.karasiq.nanoboard.server.streaming.BitMessagePublisher
import com.karasiq.nanoboard.server.utils.{CaptchaLoader, MessageValidator}
import com.karasiq.nanoboard.sources.bitmessage.BitMessageTransport
import com.karasiq.nanoboard.sources.png.UrlPngSource
import com.karasiq.nanoboard.{NanoboardCategory, NanoboardLegacy, NanoboardMessage}
import com.typesafe.config.ConfigFactory
import slick.driver.H2Driver.api._
import slick.jdbc.meta.MTable

import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.language.postfixOps
import scala.util.{Failure, Success}

object Main extends App {
  // Initialize configuration
  val config = {
    val default = ConfigFactory.load()
    val external = Paths.get(default.getString("nanoboard.external-config-file"))
    if (Files.isRegularFile(external)) {
      ConfigFactory.parseFile(external.toFile)
        .withFallback(default)
        .resolve()
    } else {
      default
    }
  }

  implicit val actorSystem = ActorSystem("nanoboard-server", config)
  implicit val executionContext = actorSystem.dispatcher
  implicit val actorMaterializer = ActorMaterializer(ActorMaterializerSettings(actorSystem))
  val db = Database.forConfig("nanoboard.database", config)

  actorSystem.registerOnTermination(db.close())

  Runtime.getRuntime.addShutdownHook(new Thread(new Runnable {
    override def run(): Unit = {
      actorSystem.log.info("Shutting down nanoboard-server")
      Await.result(actorSystem.terminate(), Duration.Inf)
    }
  }))

  // Initialize database
  def createSchema = DBIO.seq(
    containers.schema.create,
    posts.schema.create,
    deletedPosts.schema.create,
    pendingPosts.schema.create,
    categories.schema.create,
    places.schema.create,
    categories += NanoboardCategory("bdd4b5fc1b3a933367bc6830fef72a35", "Metacategory"),
    categories ++= NanoboardLegacy.categoriesFromTxt("categories.txt"),
    places ++= NanoboardLegacy.placesFromTxt("places.txt")
  )

  val schema = Source.fromPublisher(db.stream(MTable.getTables))
    .runWith(Sink.headOption)
    .flatMap {
      case None ⇒
        db.run(createSchema)
      case _ ⇒
        Future.successful(())
    }

  // Initialize server
  actorSystem.log.info("Loading captcha file")
  schema.flatMap(_ ⇒ CaptchaLoader.load(config)).foreach { captcha ⇒
    actorSystem.registerOnTermination(captcha.close())
    actorSystem.log.info("Captcha file loaded successfully ({} entries)", captcha.length)
    val messageValidator = MessageValidator(captcha, config)

    // Initialize transport
    val bitMessage = BitMessageTransport(config)
    val bitMessagePublisher = actorSystem.actorOf(BitMessagePublisher.props(bitMessage), "bitMessagePublisher")
    val dispatcher = NanoboardSlickDispatcher(db, captcha, config, Sink.foreach(actorSystem.eventStream.publish))

    // Bitmessage
    if (config.getBoolean("nanoboard.bitmessage.receive")) {
      val host = config.getString("nanoboard.bitmessage.listen-host")
      val port = config.getInt("nanoboard.bitmessage.listen-port")
      bitMessage.receiveMessages(host, port, Sink.foreach { (message: NanoboardMessage) ⇒
        messageValidator.isMessageValid(message)
          .filter(identity)
          .foreach(_ ⇒ dispatcher.addPost(s"bitmessage://${LocalDate.now()}", message))
      })
    }

    // Imageboards PNG
    val messageSource = UrlPngSource(config)
    val updateInterval = FiniteDuration(config.getDuration("nanoboard.scheduler.update-interval", TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)
    val maxNewPosts = config.getInt("nanoboard.scheduler.posts-per-container")
    val placeFlow = Flow[String]
      .named("board-png-flow")
      .flatMapMerge(4, messageSource.imagesFromPage)
      .mapAsync(1)(url ⇒ db.run(for (e ← containers.filter(_.url === url).exists.result) yield (e, url)))
      .filterNot(_._1)
      .map(_._2)
      .log("board-png-source")
      .flatMapMerge(4, url ⇒ messageSource.messagesFromImage(url).fold(Vector.empty[NanoboardMessage])(_ :+ _).map((url, _)))
      .withAttributes(ActorAttributes.supervisionStrategy(Supervision.restartingDecider) and
        Attributes.logLevels(Logging.InfoLevel, onFailure = Logging.WarningLevel))

    Source.tick(10 seconds, updateInterval, akka.NotUsed)
      .flatMapConcat(_ ⇒ Source.fromPublisher(db.stream(Place.list())))
      .via(placeFlow)
      .runForeach {
        case (url, messages) ⇒
          def insertMessages(messages: Seq[NanoboardMessage], inserted: Int = 0): Unit = messages match {
            case Seq(message, ms @ _*) if inserted < maxNewPosts ⇒
              messageValidator.isMessageValid(message)
                .flatMap(valid ⇒ if (valid) dispatcher.addPost(url, message) else Future.successful(0))
                .foreach(i ⇒ insertMessages(ms, inserted + i))

            case Seq(message) if inserted < maxNewPosts ⇒
              messageValidator.isMessageValid(message)
                .filter(identity)
                .foreach(_ ⇒ dispatcher.addPost(url, message))

            case _ ⇒
              ()
          }

          db.run(Container.create(url)).foreach { _ ⇒
            insertMessages(messages)
          }
      }

    // REST server
    val server = NanoboardServer(dispatcher)
    val host = config.getString("nanoboard.server.host")
    val port = config.getInt("nanoboard.server.port")
    Http().bindAndHandle(server.route, host, port).onComplete {
      case Success(ServerBinding(address)) ⇒
        actorSystem.log.info("Nanoboard server listening at {}", address)

      case Failure(exc) ⇒
        actorSystem.log.error(exc, "Port binding failure")
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/server/NanoboardServer.scala version [97ec7db633].













































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package com.karasiq.nanoboard.server

import scala.concurrent.ExecutionContext
import scala.util.{Failure, Success}

import akka.actor.ActorSystem
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.MediaType.Compressible
import akka.http.scaladsl.model.headers.{`Cache-Control`, CacheDirectives}
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import akka.util.ByteString
import boopickle.Default._

import com.karasiq.nanoboard.{NanoboardCategory, NanoboardMessage}
import com.karasiq.nanoboard.api.{NanoboardCaptchaAnswer, NanoboardReply}
import com.karasiq.nanoboard.dispatcher.NanoboardDispatcher
import com.karasiq.nanoboard.server.streaming.NanoboardMessageStream
import com.karasiq.nanoboard.server.utils.{AttachmentGenerator, FractalMusic}

object NanoboardServer {
  def apply(dispatcher: NanoboardDispatcher)(implicit actorSystem: ActorSystem, actorMaterializer: ActorMaterializer): NanoboardServer = {
    new NanoboardServer(dispatcher)
  }
}

private[server] final class NanoboardServer(dispatcher: NanoboardDispatcher)(implicit actorSystem: ActorSystem, actorMaterializer: ActorMaterializer) extends BinaryMarshaller {
  private implicit def ec: ExecutionContext = actorSystem.dispatcher

  private val maxPostSize = actorSystem.settings.config.getMemorySize("nanoboard.max-post-size").toBytes

  val route = {
    get {
      // Single post
      path("post" / NanoboardMessage.HashFormat) { hash ⇒
        complete(StatusCodes.OK, dispatcher.post(hash))
      } ~
      (pathPrefix("posts") & parameters('offset.as[Long].?(0), 'count.as[Long].?(100))) { (offset, count) ⇒
        // Thread
        path(NanoboardMessage.HashFormat) { hash ⇒
          complete(StatusCodes.OK, dispatcher.thread(hash, offset, count))
        } ~
        // Recent posts
        pathEndOrSingleSlash {
          complete(StatusCodes.OK, dispatcher.recent(offset, count))
        }
      } ~
      // Pending posts
      (path("pending") & parameters('offset.as[Long].?(0), 'count.as[Long].?(100))) { (offset, count) ⇒
        complete(StatusCodes.OK, dispatcher.pending(offset, count))
      } ~
      // Categories
      path("categories") {
        complete(StatusCodes.OK, dispatcher.categories())
      } ~
      // Places
      path("places") {
        complete(StatusCodes.OK, dispatcher.places())
      } ~
      // Containers
      (path("containers") & parameters('offset.as[Long].?(0), 'count.as[Long].?(100))) { (offset, count) ⇒
        complete(StatusCodes.OK, dispatcher.containers(offset, count))
      } ~
      // Fractal music renderer
      (path("fractal_music" / Segment) & respondWithHeaders(`Cache-Control`(CacheDirectives.public, CacheDirectives.`max-age`(100000000L)))) { formula ⇒
        complete(StatusCodes.OK, FractalMusic(formula).map(HttpEntity(ContentType(MediaType.audio("wav", Compressible)), _)))
      } ~
      // Verification data
      (path("verify" / NanoboardMessage.HashFormat)) { hash ⇒
        complete(StatusCodes.OK, dispatcher.requestVerification(hash))
      } ~
      // Static files
      encodeResponse(pathEndOrSingleSlash(getFromResource("webapp/index.html")) ~ getFromResourceDirectory("webapp"))
    } ~
    post {
      // New reply
      (path("post") & entity(as[NanoboardReply](defaultUnmarshaller))) { case NanoboardReply(parent, message) ⇒
        if (message.length <= maxPostSize) {
          complete(StatusCodes.OK, dispatcher.reply(parent, message))
        } else {
          complete(StatusCodes.custom(400, s"Message is too long. Max size is $maxPostSize bytes"), HttpEntity(""))
        }
      } ~
      // Create container
      (path("container") & parameters('pending.as[Int].?(10), 'random.as[Int].?(50), 'format.?("png")) & entity(as[ByteString]) & extractLog) { (pending, random, format, entity, log) ⇒
        onComplete(dispatcher.createContainer(pending, random, format, entity)) {
          case Success(data) ⇒
            complete(StatusCodes.OK, HttpEntity(data))

          case Failure(exc) ⇒
            log.error(exc, "Container creation error")
            complete(StatusCodes.custom(500, "Container creation error"), HttpEntity(ByteString.empty))
        }
      } ~
      // Generate attachment
      (path("attachment") & parameters('format.?("jpeg"), 'size.as[Int].?(500), 'quality.as[Int].?(70)) & entity(as[ByteString])) { (format, size, quality, data) ⇒
        complete(StatusCodes.OK, HttpEntity(ContentTypes.`text/plain(UTF-8)`, AttachmentGenerator.createImage(format, size, quality, data)))
      } ~
      // Verify post
      (path("verify") & entity[NanoboardCaptchaAnswer](defaultUnmarshaller)) { answer ⇒
        complete(StatusCodes.OK, dispatcher.verifyPost(answer.request, answer.answer))
      }
    } ~
    delete {
      // Delete single post
      path("post" / NanoboardMessage.HashFormat) { hash ⇒
        extractLog { log ⇒
          log.info("Post permanently deleted: {}", hash)
          complete(StatusCodes.OK, dispatcher.delete(hash))
        }
      } ~
      // Delete container posts
      (path("posts") & parameter('container.as[Long])) { container ⇒
        complete(StatusCodes.OK, dispatcher.clearContainer(container))
      } ~
      // Delete post from pending list
      path("pending" / NanoboardMessage.HashFormat) { hash ⇒
        complete(StatusCodes.OK, dispatcher.markAsNotPending(hash))
      } ~
      // Batch delete recent posts
      (path("posts") & parameters('offset.as[Long].?(0), 'count.as[Long])) { (offset, count) ⇒ // Batch delete
        complete(StatusCodes.OK, dispatcher.delete(offset, count))
      } ~
      // Clear deleted posts cache
      path("deleted") {
        complete(StatusCodes.OK, dispatcher.clearDeleted())
      }
    } ~
    put {
      // Update places list
      (path("places") & entity(as[Seq[String]](defaultUnmarshaller)) & extractLog) { (places, log) ⇒
        log.info("Places updated: {}", places)
        complete(StatusCodes.OK, dispatcher.updatePlaces(places))
      } ~
      // Update categories list
      (path("categories") & entity(as[Seq[NanoboardCategory]](defaultUnmarshaller)) & extractLog) { (categories, log) ⇒
        log.info("Categories updated: {}", categories)
        complete(StatusCodes.OK, dispatcher.updateCategories(categories))
      } ~
      // Add post to pending list
      path("pending" / NanoboardMessage.HashFormat) { hash ⇒
        complete(StatusCodes.OK, dispatcher.markAsPending(hash))
      }
    } ~
    // Event channel
    path("live") {
      handleWebSocketMessages(NanoboardMessageStream.flow)
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/server/streaming/BitMessagePublisher.scala version [551a708881].





















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.karasiq.nanoboard.server.streaming

import akka.actor.{Actor, ActorLogging, Props}
import com.karasiq.nanoboard.model.MessageConversions
import com.karasiq.nanoboard.sources.bitmessage.BitMessageTransport
import com.karasiq.nanoboard.streaming.NanoboardEvent

import scala.util.{Failure, Success}

private[server] object BitMessagePublisher {
  def props(bitMessage: BitMessageTransport) = {
    Props(classOf[BitMessagePublisher], bitMessage)
  }
}

private[server] class BitMessagePublisher(bitMessage: BitMessageTransport) extends Actor with ActorLogging {
  import context.dispatcher

  override def preStart(): Unit = {
    super.preStart()
    context.system.eventStream.subscribe(self, classOf[NanoboardEvent])
  }

  override def postStop(): Unit = {
    context.system.eventStream.unsubscribe(self)
    super.postStop()
  }

  override def receive: Receive = {
    case NanoboardEvent.PostVerified(message) ⇒
      log.debug("Sending message to BM transport: {}", message)
      bitMessage.sendMessage(MessageConversions.unwrapToMessage(message)).onComplete {
        case Success(response) ⇒
          log.info("Message was sent to BM transport: {}", response)

        case Failure(exc) ⇒
          if (log.isDebugEnabled) {
            log.error(exc, "Error sending message to BM transport: {}", message)
          }
      }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/server/streaming/NanoboardMessagePublisher.scala version [2cf6e9f307].



















































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.karasiq.nanoboard.server.streaming

import akka.stream.actor.ActorPublisher
import akka.stream.actor.ActorPublisherMessage.{Cancel, Request}
import com.karasiq.nanoboard.streaming.NanoboardEvent

import scala.annotation.tailrec

private[server] class NanoboardMessagePublisher extends ActorPublisher[NanoboardEvent] {
  override def preStart(): Unit = {
    super.preStart()
    context.system.eventStream.subscribe(self, classOf[NanoboardEvent])
  }

  override def postStop(): Unit = {
    context.system.eventStream.unsubscribe(self)
    super.postStop()
  }

  val maxBufferSize = 20
  var messageBuffer = Vector.empty[NanoboardEvent]

  override def receive: Receive = {
    case m: NanoboardEvent ⇒
      if (messageBuffer.isEmpty && totalDemand > 0) {
        onNext(m)
      } else {
        if (messageBuffer.length >= maxBufferSize) {
          messageBuffer = messageBuffer.tail :+ m
        } else {
          messageBuffer :+= m
        }
        deliverBuffer()
      }

    case Request(_) ⇒
      deliverBuffer()

    case Cancel ⇒
      context.stop(self)
  }

  @tailrec final def deliverBuffer(): Unit = {
    if (totalDemand > 0) {
      if (totalDemand <= Int.MaxValue) {
        val (use, keep) = messageBuffer.splitAt(totalDemand.toInt)
        messageBuffer = keep
        use foreach onNext
      } else {
        val (use, keep) = messageBuffer.splitAt(Int.MaxValue)
        messageBuffer = keep
        use foreach onNext
        deliverBuffer()
      }
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/server/streaming/NanoboardMessageStream.scala version [dac0877cea].























































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package com.karasiq.nanoboard.server.streaming

import scala.concurrent.duration._
import scala.language.postfixOps

import akka.actor.Props
import akka.http.scaladsl.model.ws.{BinaryMessage, Message, TextMessage}
import akka.stream._
import akka.stream.scaladsl.{Flow, GraphDSL, Source}
import akka.stream.stage.{GraphStage, GraphStageLogic, InHandler, OutHandler}
import akka.util.ByteString
import boopickle.Default._

import com.karasiq.nanoboard.streaming.{NanoboardEvent, NanoboardEventSeq, NanoboardSubscription}
import com.karasiq.nanoboard.streaming.NanoboardSubscription.{PostHashes, Unfiltered}

private[server] final class NanoboardMessageStream extends GraphStage[FanInShape2[NanoboardSubscription, NanoboardEvent, NanoboardEvent]] {
  val input: Inlet[NanoboardSubscription] = Inlet("SubscriptionInput")
  val events: Inlet[NanoboardEvent] = Inlet("EventStream")
  val output: Outlet[NanoboardEvent] = Outlet("EventOutput")

  override def shape = new FanInShape2(input, events, output)

  override def createLogic(inheritedAttributes: Attributes) = new GraphStageLogic(shape) {
    private var subscription: NanoboardSubscription = PostHashes(Set.empty)

    def request(): Unit = {
      if (!hasBeenPulled(events)) {
        pull(events)
      }
      if (!hasBeenPulled(input)) {
        pull(input)
      }
    }

    setHandler(input, new InHandler {
      override def onPush(): Unit = {
        subscription = grab(input)
        request()
      }
    })

    setHandler(events, new InHandler {
      override def onPush(): Unit = {
        grab(events) match {
          case added @ NanoboardEvent.PostAdded(message) ⇒
            subscription match {
              case PostHashes(hashes) ⇒
                if (hashes.exists(message.parent.contains) || hashes.contains(message.hash)) {
                  emit(output, added)
                }

              case Unfiltered ⇒
                emit(output, added)
            }

          case event ⇒
            emit(output, event)
        }

        request()
      }
    })

    setHandler(output, new OutHandler {
      override def onPull(): Unit = {
        request()
      }
    })
  }
}

private[server] object NanoboardMessageStream {
  import com.karasiq.nanoboard.streaming.NanoboardSubscription._

  def flow = Flow.fromGraph(GraphDSL.create() { implicit b: GraphDSL.Builder[akka.NotUsed] ⇒
    import GraphDSL.Implicits._
    val in = b.add {
      Flow[Message]
        .named("websocketInput")
        .flatMapConcat {
          case bm: BinaryMessage ⇒
            bm.dataStream.fold(ByteString.empty)(_ ++ _)

          case tm: TextMessage ⇒
            tm.textStream.fold("")(_ ++ _).map(ByteString(_))
        }
        .map(bs ⇒ Unpickle[NanoboardSubscription].fromBytes(bs.toByteBuffer))
    }

    val out = b.add {
      Flow[NanoboardEvent]
        .groupedWithin(1000, 5 seconds)
        .filter(_.nonEmpty)
        .map(events ⇒ BinaryMessage(ByteString(Pickle.intoBytes(NanoboardEventSeq(events)))))
        .named("websocketOutput")
    }

    val messages = b.add(Source.actorPublisher[NanoboardEvent](Props[NanoboardMessagePublisher]))
    val processor = b.add(new NanoboardMessageStream)

    in.out ~> processor.in0
    messages.out ~> processor.in1
    processor.out ~> out.in
    FlowShape(in.in, out.out)
  })
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/server/utils/AttachmentGenerator.scala version [d2e225e272].





















































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package com.karasiq.nanoboard.server.utils

import java.awt.RenderingHints._
import java.awt.image._
import java.awt.{Dimension, Image, Toolkit}
import java.io.ByteArrayOutputStream
import javax.imageio.stream.ImageOutputStream
import javax.imageio.{IIOImage, ImageIO, ImageWriteParam, ImageWriter}

import akka.util.ByteString
import org.apache.commons.codec.binary.Base64
import org.apache.commons.io.IOUtils

import scala.collection.JavaConversions._

object AttachmentGenerator {
  private val resizer = new ImageResizingUtil

  def createImage(format: String, size: Int, quality: Int, data: ByteString): ByteString = {
    ByteString(Base64.encodeBase64(resizer.compress(resizer.resize(data.toArray, size), format, quality)))
  }
}

private[utils] final class ImageResizingUtil {
  private def getScaledDimension(imgSize: Dimension, boundary: Dimension): Dimension = {
    var newWidth: Int = imgSize.width
    var newHeight: Int = imgSize.height
    if (imgSize.width > boundary.width) {
      newWidth = boundary.width
      newHeight = (newWidth * imgSize.height) / imgSize.width
    }
    if (newHeight > boundary.height) {
      newHeight = boundary.height
      newWidth = (newHeight * imgSize.width) / imgSize.height
    }
    new Dimension(newWidth, newHeight)
  }

  private def redrawImage(img: Image, size: Dimension): BufferedImage = {
    val bufferedImage = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB)
    val graphics = bufferedImage.createGraphics()
    try {
      // Max quality settings
      graphics.setRenderingHints(Map(
        KEY_RENDERING → VALUE_RENDER_QUALITY,
        KEY_COLOR_RENDERING → VALUE_COLOR_RENDER_QUALITY,
        KEY_ANTIALIASING → VALUE_ANTIALIAS_ON,
        KEY_INTERPOLATION → VALUE_INTERPOLATION_BICUBIC
      ))
      graphics.drawImage(img, 0, 0, size.width, size.height, null)
      bufferedImage
    } finally {
      graphics.dispose()
    }
  }

  // JPG colors fix
  private def loadImage(image: Array[Byte]): BufferedImage = {
    val img = Toolkit.getDefaultToolkit.createImage(image)

    val RGB_MASKS: Array[Int] = Array(0xFF0000, 0xFF00, 0xFF)
    val RGB_OPAQUE: ColorModel = new DirectColorModel(32, RGB_MASKS(0), RGB_MASKS(1), RGB_MASKS(2))

    val pg = new PixelGrabber(img, 0, 0, -1, -1, true)
    pg.grabPixels()

    (pg.getWidth, pg.getHeight, pg.getPixels) match {
      case (width, height, pixels: Array[Int]) ⇒
        val buffer = new DataBufferInt(pixels, width * height)
        val raster = Raster.createPackedRaster(buffer, width, height, width, RGB_MASKS, null)
        new BufferedImage(RGB_OPAQUE, raster, false, null)

      case _ ⇒
        throw new IllegalArgumentException("Invalid image")
    }
  }

  def compress(image: BufferedImage, format: String, quality: Int): Array[Byte] = {
    val outputStream = new ByteArrayOutputStream()
    val imageOutputStream: ImageOutputStream = ImageIO.createImageOutputStream(outputStream)
    try {
      val writer: ImageWriter = ImageIO.getImageWritersByFormatName(format).next
      val iwp: ImageWriteParam = writer.getDefaultWriteParam
      if (iwp.canWriteCompressed) {
        iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT)
        iwp.setCompressionQuality(quality.toFloat / 100.0f)
      }
      writer.setOutput(imageOutputStream)
      writer.write(null, new IIOImage(image, null, null), iwp)
      writer.dispose()
      imageOutputStream.flush()
      outputStream.toByteArray
    } finally {
      IOUtils.closeQuietly(imageOutputStream)
      IOUtils.closeQuietly(outputStream)
    }
  }

  def resize(data: Array[Byte], size: Int): BufferedImage = {
    val image = loadImage(data)
    val imgSize = new Dimension(image.getWidth, image.getHeight)
    val boundary = new Dimension(size, size)
    val newSize = getScaledDimension(imgSize, boundary)
    redrawImage(image, newSize)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/server/utils/CaptchaLoader.scala version [6217b05108].



















































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.karasiq.nanoboard.server.utils

import java.nio.file.{Files, Path, Paths}

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.headers.Location
import akka.http.scaladsl.model.{HttpRequest, HttpResponse, Uri}
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{FileIO, Source}
import com.karasiq.nanoboard.captcha.storage.{NanoboardCaptchaFileSource, NanoboardCaptchaSource}
import com.typesafe.config.{Config, ConfigFactory}

import scala.concurrent.{ExecutionContext, Future}

object CaptchaLoader {
  def apply(config: Config = ConfigFactory.load())(implicit am: ActorMaterializer, as: ActorSystem, ec: ExecutionContext): CaptchaLoader = {
    new CaptchaLoader(Paths.get(config.getString("nanoboard.captcha.storage")))
  }

  def load(config: Config = ConfigFactory.load())(implicit am: ActorMaterializer, as: ActorSystem, ec: ExecutionContext): Future[NanoboardCaptchaFileSource] = {
    apply(config).forUrl(config.getString("nanoboard.captcha.download-url"))
  }
}

final class CaptchaLoader(baseDir: Path)(implicit am: ActorMaterializer, as: ActorSystem, ec: ExecutionContext) {
  private val http = Http()

  // TODO: https://github.com/akka/akka/issues/15990
  private def requestWithRedirects(uri: Uri): Future[HttpResponse] = {
    http.singleRequest(HttpRequest(uri = uri)).flatMap { response ⇒
      val location = response.header[Location]
      if (response.status.isRedirection() && location.isDefined) {
        requestWithRedirects(location.get.uri)
      } else {
        Future.successful(response)
      }
    }
  }

  def forUrl(url: String): Future[NanoboardCaptchaFileSource] = {
    val fileName = baseDir.resolve(s"${Integer.toHexString(url.hashCode)}.nbc")
    if (Files.exists(fileName)) {
      assert(Files.isRegularFile(fileName), s"Not a file: $fileName")
      Future.successful(NanoboardCaptchaSource.fromFile(fileName.toString))
    } else {
      Source
        .fromFuture(requestWithRedirects(url))
        .flatMapConcat(_.entity.dataBytes)
        .toMat(FileIO.toFile(fileName.toFile))((_, r) ⇒ r.map { ioResult ⇒
          if (ioResult.wasSuccessful) NanoboardCaptchaSource.fromFile(fileName.toString)
          else throw ioResult.getError
        })
        .run()
    }
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/server/utils/FractalMusic.scala version [051f3de01a].











































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.karasiq.nanoboard.server.utils

import java.util.concurrent.{Executors, TimeUnit, TimeoutException}
import javax.script.ScriptEngine

import akka.util.ByteString
import jdk.nashorn.api.scripting.NashornScriptEngineFactory

import scala.concurrent.duration.{FiniteDuration, _}
import scala.concurrent.{Future, Promise}
import scala.language.postfixOps

object FractalMusic extends FractalMusicGenerator

class FractalMusicGenerator {
  private val scheduler = Executors.newScheduledThreadPool(1)
  private val engineFactory = new NashornScriptEngineFactory()

  protected def createScriptEngine(): ScriptEngine = {
    engineFactory.getScriptEngine(Array("-strict", "--no-java", "--no-syntax-extensions"), getClass.getClassLoader)
  }

  def apply(formula: String, timeLimit: FiniteDuration = 5 seconds): Future[ByteString] = {
    val source =
      s"""
         |(function(){
         |  var sampleRate = 8000;
         |  var data = []
         |  for (var t = 0; t < 4*65536; t++) {
         |    data[t] = $formula;
         |    data[t] = (data[t] & 0xff) / 256.0;
         |  }
         |
         |  var n = data.length;
         |  var integer = 0, i;
         |  var header = 'RIFF<##>WAVEfmt \\x10\\x00\\x00\\x00\\x01\\x00\\x01\\x00<##><##>\\x01\\x00\\x08\\x00data<##>';
         |
         |  function insertLong(value) {
         |    var bytes = "";
         |    for (i = 0; i < 4; ++i) {
         |      bytes += String.fromCharCode(value % 256);
         |      value = Math.floor(value / 256);
         |    }
         |    header = header.replace('<##>', bytes);
         |  }
         |
         |  insertLong(36 + n);
         |  insertLong(sampleRate);
         |  insertLong(sampleRate);
         |  insertLong(n);
         |
         |  for (var i = 0; i < n; ++i) {
         |    header += String.fromCharCode(Math.round(Math.min(1, Math.max(-1, data[i])) * 127 + 127));
         |  }
         |  return header;
         |})();
    """.stripMargin

    val promise = Promise[ByteString]

    val thread = new Thread(new Runnable {
      override def run(): Unit = {
        try {
          val engine = createScriptEngine()
          promise.success(ByteString(engine.eval(source).asInstanceOf[String].toCharArray.map(_.toByte)))
        } catch {
          case exc: Throwable ⇒
            promise.failure(exc)
        }
      }
    })

    scheduler.schedule(new Runnable {
      override def run(): Unit = {
        if (!promise.isCompleted) {
          thread.interrupt()
          promise.failure(new TimeoutException("JavaScript execution timed out"))
        }
      }
    }, timeLimit.toMillis, TimeUnit.MILLISECONDS)

    thread.start()
    promise.future
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/main/scala/com/karasiq/nanoboard/server/utils/MessageValidator.scala version [3beb7a826f].







































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.karasiq.nanoboard.server.utils

import scala.collection.JavaConversions._
import scala.concurrent.{ExecutionContext, Future}

import com.typesafe.config.{Config, ConfigFactory}

import com.karasiq.nanoboard.NanoboardMessage
import com.karasiq.nanoboard.captcha.{NanoboardCaptcha, NanoboardPow}
import com.karasiq.nanoboard.captcha.storage.NanoboardCaptchaSource

private[server] object MessageValidator {
  def apply(captcha: NanoboardCaptchaSource, config: Config = ConfigFactory.load())(implicit ec: ExecutionContext): MessageValidator = {
    new MessageValidator(captcha, config)
  }
}

private[server] final class MessageValidator(captcha: NanoboardCaptchaSource, config: Config)(implicit ec: ExecutionContext) {
  private[this] val requirePow = config.getBoolean("nanoboard.pow-required")
  private[this] val maxPostSize = config.getMemorySize("nanoboard.max-post-size").toBytes
  private[this] val spamFilter = config.getStringList("nanoboard.scheduler.spam-filter").toVector
  private[this] val powCalculator = NanoboardPow(config)

  def isMessageValid(message: NanoboardMessage): Future[Boolean] = {
    Future.reduce(Seq(
      Future.successful(
        message.parent.matches(NanoboardMessage.HashFormat.regex) &&
          message.text.nonEmpty &&
          message.text.length <= maxPostSize &&
          spamFilter.forall(!message.text.matches(_))
      ),
      if (requirePow) NanoboardCaptcha.verify(message, powCalculator, captcha) else Future.successful(true)
    ))(_ && _)
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/test/resources/application.conf version [73cf384a3a].





















>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
nanoboard {
  test-database {
    url = "jdbc:h2:mem:test1"
    driver = org.h2.Driver
    connectionPool = disabled
    keepAliveConnection = true
  }
}

akka.loglevel = DEBUG

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/test/scala/com/karasiq/nanoboard/server/test/DatabaseTest.scala version [cf0f293518].



































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.karasiq.nanoboard.server.test

import com.karasiq.nanoboard.NanoboardMessageGenerator
import com.karasiq.nanoboard.api.NanoboardMessageData
import com.karasiq.nanoboard.model._
import org.scalatest.{BeforeAndAfterAll, FlatSpec, Matchers}
import slick.driver.H2Driver.api._

import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

class DatabaseTest extends FlatSpec with Matchers with BeforeAndAfterAll {
  val testMessage = NanoboardMessageGenerator().newMessage("8b8cfb7574741838450e286909e8fd1f", "Hello world!")
  val db = Database.forConfig("nanoboard.test-database")

  "Database" should "add entry" in {
    println(testMessage)
    assert(testMessage.parent.length == testMessage.hash.length)

    val query = for {
      _ ← DBIO.seq(containers.schema.create, posts.schema.create, deletedPosts.schema.create, pendingPosts.schema.create, categories.schema.create)
      c ← Container.forUrl("local://test")
      _ ← Post.insertMessage(c, testMessage)
      message ← Post.get(testMessage.hash)
    } yield message

    val result: Option[NanoboardMessageData] = Await.result(db.run(query), Duration.Inf)
    println(result)

    result.get.hash shouldBe testMessage.hash
    val answers: Vector[NanoboardMessageData] = Await.result(db.run(Post.thread("8b8cfb7574741838450e286909e8fd1f", 0, 10)), Duration.Inf)
    answers.map(_.hash) shouldBe Vector(testMessage.hash)

    Await.result(db.run(Container.forUrl("local://test")), Duration.Inf) shouldBe result.get.containerId.get
  }

  it should "delete entry" in {
    val delete = Post.delete(testMessage.hash)
    val query = for (_ ← delete; ps ← posts.result) yield ps
    val result = Await.result(db.run(query), Duration.Inf)
    result shouldBe empty
  }

  override protected def afterAll(): Unit = {
    db.close()
    super.afterAll()
  }
}

Added wiki_references/2017/software/steganography_related_references/karasiq-nanoboard/src_from_GitHub/the_repository_clones/nanoboard/src/test/scala/com/karasiq/nanoboard/server/test/FractalMusicTest.scala version [ca419ceff6].











































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.karasiq.nanoboard.server.test

import com.karasiq.nanoboard.encoding.NanoboardCrypto._
import com.karasiq.nanoboard.server.utils.FractalMusic
import com.karasiq.nanoboard.utils._
import org.scalatest.{FlatSpec, Matchers}

import scala.concurrent.duration._
import scala.concurrent.{Await, TimeoutException}
import scala.language.postfixOps

class FractalMusicTest extends FlatSpec with Matchers {
  "Fractal music generator" should "generate WAV from formula" in {
    val result = Await.result(FractalMusic("(t%((t>>3)+((t>>2)%2?-5:-10)/t>>8&(130+((t%65536)>>10))))<<2", 10 minutes), Duration.Inf)
    sha256.digest(result).toHexString() shouldBe "5b19d273699ecb5009bdf505221c4cd05cde3cb8d81b35d34dc0ca19b472f815"
  }

  it should "halt the execution of infinite loop" in {
    intercept[TimeoutException](Await.result(FractalMusic("(function(){while(true){};})()", 1 seconds), Duration.Inf))
  }
}