an imageboard in sh https://sose.tilde.team/shi/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

383 lines
10 KiB

#!/bin/sh
# TODO: gif support and thumbnails if Imagemagick is installed
# TODO: More comments
# TODO: discard reply if parent doesnt exist, right now shi hangs
backend="./http_backend.sh"
db_backend="./file_db_backend.sh"
. ./globals.sh
. ./util.sh
. "$backend"
. "$db_backend"
sanitize_text() {
# Sanitizes text, the first argument is the text to be
# sanitized, the rest represent what sanitations should be applied
# (html, sed, etc.)
# sed sanitation will prepend a leading backslash to sed characters
# html sanitation will escape '&', '<', and '>' and replace newlines
# with '<br>' tags
# NOTE: 'sed' must be placed before 'html" in the options if they
# are both specified (This should be fixed later)
local text="$1" # text to be sanitized
for option in "$@" # parse options to find specified sanitations
do
case "$option" in "$text")
;; # if it is the first option, ignore it
"sed")
echo "sanitizing text $text for $option" >> $log_file
text="$(echo "$text" \
| sed -e 's/&/&amp;/g' \
-e 's/</\&lt;/g' \
-e 's/>/\&gt;/g' \
-e 's/\([-$^*()+{\[\.?\/]\)/\\\1/g')"
;;
"html")
echo "sanitizing text $text for $option" >> $log_file
text="$(echo "$text" \
| sed -e ':a;N;$!ba;s/\n/<br>/g')"
;;
esac
done
echo "$text"
}
get_op_post_html() {
# Get the html of an op post if it has already been generated
local temp_file="$(mktemp)"
local post_db_path="$1"
local post_html_path="$(get_post_data "$post_db_path" "html_path")"
local post_id="$(get_post_data "$post_db_path" "post_id")"
local trim="$2" # how many replies to get
local post_replies="$(get_post_data "$post_db_path" "replies")"
local reply_num="$(echo "$post_replies" | wc -l)" # number of replies to the post
cat "$post_html_path" \
| sed -e ':a' -e 'N' -e '$!ba' \
-e "s/.*\(<!--BEGIN OP POST $post_id-->.*<!--END OP POST $post_id-->\).*/\1/g" \
> "$temp_file"
if [ $reply_num -gt $trim ]
then
# id of the most recent reply to be included on the board page
local reply_lim="$(tac "$post_html_path" \
| grep -E "<!--END REPLY [0-9]+-->" \
| head -n "$trim" \
| tail -n1 | grep -Eo '[0-9]+')"
sed -i -e ':a' -e 'N' -e '$!ba' \
-e "s/<!--BEGIN REPLY [0-9]\+-->.*<!--BEGIN REPLY $reply_lim-->/<!--BEGIN REPLY $reply_lim-->/g" \
"$temp_file"
cat "$temp_file"
else
cat "$temp_file"
fi
rm "$temp_file"
}
gen_post_html() {
# Takes a path to a database entry and generates html
# based on it's content
# TODO: implement thumbnails
# TODO: separate html frontend to enable creation of other frontends
# eval
local post_db_path="$1" # db path to post
local template_path="" # filesystem path to the post template to be used
echo "[Genereate post html]" >> "$log_file"
echo "Generating post html for $post_db_path" >> "$log_file"
local date="$(date -u)"
local user="$(get_post_data "$post_db_path" "user")"
local title="$(get_post_data "$post_db_path" "title")"
local content="$(get_post_data "$post_db_path" "content")"
local image_path="$(get_post_data "$post_db_path" "image_path")"
local image_size=""
local image_name=""
local post_id="$(get_post_data "$post_db_path" "post_id")"
local post_type="$(get_post_data "$post_db_path" "post_type")"
# http path to post directory (generated from html path)
local post_url_path="$(get_post_data "$post_db_path" "html_path")"
post_url_path="$(dirname "$post_url_path")"
# http path to image
local image_url_path=""
# http path to thumbail
local thumb_url_path=""
# truncate the url path to start at $board_dir
case "$post_type" in "oppost")
post_url_path="$(echo "$post_url_path" \
| rev \
| cut -f -3 -d '/' \
| rev)"
;;
"reply")
post_url_path="$(echo "$post_url_path" \
| rev \
| cut -f -4 -d '/' \
| rev)"
;;
esac
post_url_path="$shi_sub_url/$post_url_path"
if [ -z "$image_path" ] # if no image is found
then
post_type="$post_type"-noimage # specify the post type as imageless
echo "Image path is null, -noimage post" >> "$log_file"
else
image_size="$(du -h "$image_path" | cut -f 1)"
image_name="$(basename "$image_path")"
image_url_path="$post_url_path/$image_name"
thumb_url_path="$post_url_path/thumb_$image_name.jpg"
fi
template_path="$template_dir/$post_type".template
content="$(sanitize_text "$content" sed html)" # sanitize content
title="$(sanitize_text "$title" sed)" # sanitize title for sed
post_url_path="$(sanitize_text "$post_url_path" sed)" # sanitize post url for sed
image_url_path="$(sanitize_text "$image_url_path" sed)" # sanitize image path for sed
thumb_url_path="$(sanitize_text "$thumb_url_path" sed)" # sanitize image path for sed
# read through the appropriate template and replace
# placeholders with their actual values
while read -r line
do
echo "$line" \
| sed -e "s/{{{ POSTNUM }}}/$post_id/g" \
-e "s/{{{ POSTURL }}}/$post_url_path/g" \
-e "s/{{{ DATE }}}/$date/g" \
-e "s/{{{ USER }}}/$user/g" \
-e "s/{{{ POSTTITLE }}}/$title/g" \
-e "s/{{{ POSTTEXT }}}/$content/g" \
-e "s/{{{ IMAGEPATH }}}/$image_url_path/g" \
-e "s/{{{ THUMBPATH }}}/$thumb_url_path/g" \
-e "s/{{{ IMAGENAME }}}/$image_name/g" \
-e "s/{{{ IMAGESIZE }}}/$image_size/g"
done < "$template_path"
}
insert_post() {
# TODO: cache (?) the position of <!--BEGIN POSTS--> in the html
# currently a large and exponential slowdown
# read from a cache file, if it doesn't exist, make it
local post_db_path="$1" # path to the post data
local target_html="$2" # the html to be modified
local trim="3" # how many replies to display on the main board page
echo "[Insert post into html]" >> "$log_file"
echo "Inserting post $post_db_path into $target_html" >> "$log_file"
local post_id="$(get_post_data "$post_db_path" "post_id")"
local post_type="$(get_post_data "$post_db_path" "post_type")"
local post_replies=""
local parent=""
local temp_file="$(mktemp)"
local replace="" # regex to find the exitsing post's html
local insert_at="" # html will be inserted after the line containing this string
case "$post_type" in "oppost")
replace="<!--BEGIN OP POST $post_id-->.*<!--END OP POST $post_id-->"
insert_at="<!--BEGIN POSTS-->"
;;
"reply")
parent="$(get_post_data "$post_db_path" "parent")"
replace="<!--BEGIN REPLY $post_id-->.*<!--END REPLY $post_id-->\n"
# find existing reply end tags in the parent post html
insert_at="$(cat "$target_html" | grep -E "<!--END REPLY [0-9]+-->")"
echo "inserting at $insert_at" >> "$log_file"
if [ -n "$insert_at" ] # if reply end tags are found
then
# set `insert_at` to the end tag of the lowermost reply
insert_at="$(echo "$insert_at" | tail -n1)"
else
# else set `insert_at` to beginning of replies
insert_at="<!--BEGIN REPLIES $parent-->"
fi
;;
esac
echo "inserting at $insert_at" >> "$log_file"
# remove any existing instances of the post in the html
sed -i \
-e ':a' \
-e 'N' \
-e '$!ba' \
-e "s/$replace//g" "$target_html"
while read -r line
do
echo "$line"
if echo "$line" | grep -qE "$insert_at"
then
echo "Inserting html at line $line matching $insert_at" >> "$log_file"
if [ "$post_type" = "oppost" ]
then
post_replies="$(get_post_data "$post_db_path" "replies")"
if [ -n "$post_replies" ]
then
get_op_post_html "$post_db_path" "$trim"
else
gen_post_html "$post_db_path"
fi
else
gen_post_html "$post_db_path"
fi
fi
done < "$target_html" > "$temp_file"
cat "$temp_file" > "$target_html"
# NOTE: This section only applies when regenerating an entire board
# If the post is an oppost, we will need to also generate the replies
# if [ "$post_type" = "oppost" ]
# then
# # NOTE: Very redundant, find a way to process both files at once
# post_replies=$(get_post_data "$post_db_path" "replies" | tac)
# for reply_db_path in $post_replies
# do
# echo "Inserting "$reply_db_path" into post html" >> "$log_file"
# insert_post "$reply_db_path" "$(get_post_data "$post_db_path" "html_path")"
# done
#
# # NOTE: This will break if the board directory has spaces
# for reply_db_path in $(echo $post_replies | cut -f "$trim"- -d ' ')
# do
# echo "Inserting "$reply_db_path" into board html" >> "$log_file"
# insert_post "$reply_db_path" "$target_html"
# done
# fi
}
start_listener() {
# This function reads a continuous stream of data
# (provided by the `listen` function from a user
# specified backend) and generates posts from the
# data it receives.
local status=0 # 0 is normal, 1 is receiving post data
local board=""
local parent=""
local user=""
local title=""
local content=""
local image_name=""
local image_content=""
local post_id=""
local post_type=""
local post_html_path=""
local latest=""
listen | while read -r line
do
if [ "$line" = "End new post" ]
then
echo [New post] >> "$log_file"
status=0
latest="$(cat "$latest_file")"
post_id="$(( latest + 1 ))"
post_db_path="$(create_post \
"$board" \
"$parent" \
"$user" \
"$title" \
"$content" \
"$image_name" \
"$image_content" \
"$post_id" \
"$post_type")"
post_html_path="$(get_post_data "$post_db_path" "html_path")"
parent_html_path=""
parent_db_path=""
if [ "$post_type" == "oppost" ]
then
cp "$board_dir/$board/index.html.template" "$post_html_path"
cp "$template_dir/main.css.template" "$(dirname "$post_html_path")/main.css"
insert_post "$post_db_path" "$post_html_path"
insert_post "$post_db_path" "$board_dir/$board/index.html"
else
parent_html_path="$board_dir/$board/$parent/index.html"
parent_db_path="$board_dir/$board/$parent"
insert_post "$post_db_path" "$parent_html_path"
insert_post "$parent_db_path" "$board_dir/$board/index.html"
fi
echo "$post_id" > "$latest_file" # update the latest post file
echo "Finished adding post $post_id at $(date)"
fi
if [ "$status" -eq 1 ]
then
key="$(echo "$line" | cut -f 1 -d :)" value="$(echo "$line" | cut -f 2- -d :)"
echo "Reading key $key from backend" >> "$log_file"
case "$key" in "board")
board="$value"
;;
"parent")
parent="$value"
if [ -z "$parent" ]
then
post_type="oppost"
else
post_type="reply"
fi
;;
"user")
user="$value"
;;
"title")
title="$value"
;;
"content")
content="$value"
;;
"image_name")
image_name="$value"
;;
"image_content")
image_content="$value"
;;
esac
fi
if [ "$line" = "New post" ]
then
echo "Processing new post" >> "$log_file"
status=1
fi
done
}
trap break EXIT INT TERM