iron/iron
2023-04-09 01:40:22 +02:00

376 lines
13 KiB
Bash
Executable file

#!/bin/bash
# iron
# Simple rautafarmi (json) textboard client.
# Licensed under GNU GPLv3.
# See https://codeberg.org/speedie/iron or https://speedie.gq/iron for more information.
# wrapper funcs
printn() { printf "%s$*"; }
printstr() { printn "%s$*\n"; }
die() { printstr "%s$*" && exitclient 1; }
definstance="${definstance:-https://example.com}"
cleancli() {
rm -f "$working_dir/iron-editor-message" \
"$working_dir/iron-messagedata" \
"$working_dir/iron-message" \
"$working_dir/iron-api" \
"$working_dir/iron-api-orig"
}
updateborder() {
sp="$(seq "$(tput cols)" | xargs -Iz printf "${color_01}$s${color_01}")"
return
}
exitclient() {
[ -z "$1" ] && exitcode="0" || exitcode="$1"
endmsg="iron terminated. (exit code $exitcode)"
# clean working dir
rm -f "$working_dir/iron-editor-message" \
"$working_dir/iron-messagedata" \
"$working_dir/iron-message" \
"$working_dir/iron-api" \
"$working_dir/iron-api-orig"
clear
printstr "$endmsg"
exit "$exitcode"
}
end_session() {
rm -f "$working_dir/iron-session" \
"$working_dir/iron-editor-message" \
"$working_dir/iron-messagedata" \
"$working_dir/iron-api" \
"$working_dir/iron-api-orig"
clear
${0}
exitclient 0
}
# init
main() {
# few basic checks
[ -z "$(command -v jq)" ] && die "fatal error: jq not found."
[ -z "$working_dir" ] && working_dir=$HOME/.local/share/iron
# check args
case "$1" in
"-h") printf "usage: iron [-c]\n" && exit 0 ;;
"") : ;;
"-c") rm -rf "$working_dir" && exit 0 ;;
*) printf "usage: iron [-c]\n" && exit 1 ;;
esac
cleancli
mkdir -p "$working_dir"
[ -e "$working_dir/iron-instance" ] || printstr "$definstance" > "$working_dir/iron-instance"
[ -e "$working_dir/iron-ckey" ] && [ -z "$ckey" ] && ckey="$(cat "$working_dir/iron-ckey")"
instance="$(cat "$working_dir/iron-instance")"
[ -z "$instance" ] && instance="example.com" # fallback
# hardcoded line count, this is the max lines our messages can take up, otherwise this code sucks
lc="$(($(tput lines) - 7))"
# ansi escape codes, by default we use 7 colors
[ -z "$NOCOLOR" ] && \
color_01="$(printf '\033[0m')"
color_02="$(printf '\033[0;33m')"
color_03="$(printf '\033[0;35m')"
color_04="$(printf '\033[0;36m')"
color_05="$(printf '\033[0;33m')"
color_06="$(printf '\033[0;31m')"
color_07="$(printf '\033[0;32m')"
name="iron"
ver="1.3"
exver="09-04-2022"
s='─'
sc="$(tput cols)"
curmode="-- NORMAL --" # default mode is normal mode
updateborder
dl_api
[ -f "$working_dir/iron-session" ] && \
printstr "Existing session exists, joining.." || ph
EDITOR="${EDITOR:-vim}"
clear
printheader
# die if no instance
if [ -z "$instance" ]; then
printn "${color_06}000 - SYSTEM at $(date '+%Y/%m/%d %T:')\nInstance is not set. Please set it!.${color_01}" && sleep 3
rm "$working_dir/iron-session"
$0 && exitclient 0
fi
[ "$ckey" != "IRON_NO_CAPTCHA" ] && [ -z "$ckey" ] && printn "${color_06}000 - SYSTEM at $(date '+%Y/%m/%d %T:')\nWarning: No captcha was provided. This may cause messages to not send properly. Set the captcha to IRON_NO_CAPTCHA to hide this warning.${color_01}\n" && sleep 2 && clear && printheader
[ "$ckey" = "IRON_NO_CAPTCHA" ] && ckey="" # then we don't need a captcha, according to the user who is probably an idiot
# check connection
ping -q "$(dirname "$instance" | sed "s|https://||g")" -c 1 > /dev/null || die "error: could not download api data"
parse_msgs
checkupt
}
dl_api()
{
# download everything from the api
curl -so "$working_dir/iron-api-orig" "$instance/api.php"
[ -e "$working_dir/iron-api-orig" ] && sed "s|\n|${color_01}\n|g" "$working_dir/iron-api-orig" > "$working_dir/iron-api" || dl_api
}
ph() {
clear
printstrh() {
printstr "$name $ver"
printstr "$sp"
printstr "${color_02}Last login: $(date '+%Y/%m/%d at %T')${color_01}"
printstr "${color_07}$name was started: '$(date '+%T')'.\n"
printstr "Welcome to $name $ver. $name is a rautafarmi textboard client with Vim keybinds."
[ -z "$instance" ] && pinstance="None set." || pinstance="$instance"
# print instance text/url
printn "\n${color_03}Instance: '$pinstance'\n${color_01}"
printn "\n${color_02}iron written by speedie & potential contributors and is licensed under the GNU General Public License version 3.0 free software license."
printn "\n${color_05}See https://speedie.gq/projects/iron.php or https://codeberg.org/speedie/iron for more information.${color_01}\n"
}
printstrh
while true; do
# get user cmd
read -r -n 1 iscmd
# check what to do
if [ "$iscmd" != ":" ]; then
case "$iscmd" in
"Z") read -r -n 1 zbind ;;
esac
clear; printstrh
else
read -r bind
fi
# ZZ and ZQ binds
case "$zbind" in
"ZZ") $0 && exitclient 0 ;;
"ZQ") exitclient 0 ;;
esac
# misc binds
case "$bind" in
"Q") exitclient 0 ;;
"Z") $0 && exitclient 0 ;;
"q") exitclient 0 ;;
"q!") exitclient 0 ;;
"quit") exitclient 0 ;;
"j") touch "$working_dir/iron-session" && main ;;
"set captcha") set_cap ;;
"set instance") clear ; printheader ; printn "Enter your instance (Example: https://example.com/ironfarms):\n> " ; read -r instance ; printf "%s" "$instance" > "$working_dir/iron-instance" && clear && printheader && \
printf "%s000 - SYSTEM at %s\nSet instance to '%s'.%s" "${color_06}" "$(date '+%Y/%m/%d %T:')" "$instance" "${color_01}" && sleep 3 && ${0} && exitclient 0 ;;
"set instance default") echo "$definstance" > "$working_dir/iron-instance" && clear && printheader && \
printf "%s000 - SYSTEM at %s\nSet instance to '%s'.%s" "${color_06}" "$(date '+%Y/%m/%d %T:')" "$definstance" "${color_01}" && sleep 3 && ${0} && exitclient 0 ;;
"reset") rm -rf "$working_dir" && ${0} && exitclient 0 ;;
esac
done
}
# print basic header and then the separator
printheader() { printf "%s %s\n%s\n" "$name" "$ver" "$sp"; }
# captcha
set_cap() {
clear
printheader
printn "Enter your captcha answer. This can often be found on the instance website. Note that if this is randomized then iron is not very convenient to use. In this case switch instance or use the official client instead. (Example: 43):\n> "
read cap
printstr "$cap" > "$working_dir/iron-ckey" && clear && printheader && printf "%s000 - SYSTEM at %s\nSet captcha to '%s'.%s" "${color_06}" "$(date '+%Y/%m/%d %T:')" "$cap" "${color_01}" && sleep 3 && ${0} && exitclient 0
}
parse_msgs() {
if [ "$do_not_reparse" != "true" ]; then
# fail if the json is not valid
failed_to_parse_json() {
# originally wanted to fail the client completely if the json isn't valid
# there is currently some weird bug though so for now we'll restart it to hide the problem
clear
rm -f "$working_dir/iron-messagedata" "$working_dir/iron-message"
$0 && exitclient 0
}
rm -f "$working_dir/iron-editor-message" \
"$working_dir/iron-messagedata" \
"$working_dir/iron-message" \
"$working_dir/iron-api" \
"$working_dir/iron-api-orig"
dl_api
# save individual data to combine later
jq -e ".posts[] .username" "$working_dir/iron-api" > "$working_dir/iron-username" || failed_to_parse_json
jq -e ".posts[] .message" "$working_dir/iron-api" > "$working_dir/iron-message" || failed_to_parse_json
jq -e ".posts[] .time" "$working_dir/iron-api" > "$working_dir/iron-messagetime" || failed_to_parse_json
jq -e ".posts[] .postID" "$working_dir/iron-api" > "$working_dir/iron-messageid" || failed_to_parse_json
jq -e ".posts[] .imageURL" "$working_dir/iron-api" > "$working_dir/iron-messageimg" || failed_to_parse_json
# delete prev message data
rm -f "$working_dir/iron-messagedata"
# format it somewhat
# we will mostly print this file as it is
# replace "" in username because it means none was specified.
# replace "%" with "%%" so that printf will actually print it.
for i in $(seq $lc); do
username="$(head -n "$i" "$working_dir/iron-username" | tail -n 1 | sed 's/""/Anonymous/g')"
message="$(head -n "$i" "$working_dir/iron-message" | tail -n 1 | sed "s/%/%%/g; s/>>/DISREPLY/g; s/>/DISGTEXT/g; s/DISREPLY/${color_04}>>/g; s/DISGTEXT/${color_07}>/g; s/\" \"//g")"
time="$(head -n "$i" "$working_dir/iron-messagetime" | tail -n 1)"
id="$(head -n "$i" "$working_dir/iron-messageid" | tail -n 1)"
imageurl="$(head -n "$i" "$working_dir/iron-messageimg" | tail -n 1 | sed 's/""/iron_no_image/g')"
# image url formatting
imgformattingb="]"
[ -z "$message" ] && [ "$imageurl" != "iron_no_image" ] && imgformattinga="[URL: "
[ ! -z "$message" ] && imgformattinga=" [URL: "
[ "$imageurl" = "iron_no_image" ] && imgformattinga="" && imgformattingb="" && imageurl=""
# print it
printf "%s%s - %s%s%s at %s%s:%s\n%s%s%s%s%s%s%s\n" \
"${color_04}" "$id" "${color_05}" "$username" "${color_02}" "${color_03}" "$time" "${color_01}" "$message" "${color_01}" "${color_04}" "$imgformattinga" "$imageurl" "$imgformattingb" "${color_01}" | sed 's/"//g' | sed "s/.*/${color_01}&${color_01}/" >> "$working_dir/iron-messagedata"
done
fi
# get the number of messages we need from the parsed list of stuff
fold -w${sc} "$working_dir/iron-messagedata" | head -n "$lc"
# print footer
printstr "${color_01}$sp"
printstr "${color_04}I: ${instance} L: ${lc}/$(wc -l < "$working_dir/iron-message") LL: $(date '+%Y/%m/%d at %T')${color_01}"
printstr "${sp}"
do_not_reparse=false
}
# send the message using curl that we composed
sendmsg()
{
[ -e "$working_dir/iron-editor-message" ] && smsg="$(cat "$working_dir/iron-editor-message")"
if [ -z "$ckey" ]; then
curl -sX POST "$instance/post.php" -F "message=$smsg" > /dev/null && curl_status=sent
else
curl -sX POST "$instance/post.php" -F "message=$smsg" -F "captcha=$ckey" > /dev/null && curl_status=sent
fi
# if it was sent properly
if [ "$curl_status" = "sent" ]; then
# print system status, if message was sent
if [ ! -z "$smsg" ]; then
clear
printheader
printn "${color_06}000 - SYSTEM at $(date '+%Y/%m/%d %T:')\nMessage '$smsg' sent. ${color_01}" && sleep 0.5
else
clear
printheader
printn "${color_06}000 - SYSTEM at $(date '+%Y/%m/%d %T:')\nError: Message cannot be empty. ${color_01}" && sleep 0.5
fi
else
clear
printheader
printn "${color_06}000 - SYSTEM at $(date '+%Y/%m/%d %T:')\nError: Server did not accept the message.\nCheck with your instance maintainer or try composing another message. ${color_01}" && sleep 0.5
fi
rm -f "$working_dir/iron-editor-message"
clear
${0} && exitclient 0
}
# check for new messages by comparing printed messages and downloaded messages
checkupt() {
while true; do
[ ! -e "$working_dir/iron-messagedata" ] && break
[ "$sc" != "$(tput cols)" ] && updateborder
check_mode
done
dl_api; parse_msgs
}
# compose a message
insert() {
clear
do_not_reparse=true # don't download new messages and parse the api. this saves time as we don't need it now anyway!
printheader
parse_msgs
printn "%s-- INSERT --\n> "
read smsg && ed=normal
[ -z "$smsg" ] && ${0} && exitclient 0
sendmsg
${0} && exitclient 0
}
# run a command and perform its action
cmd() {
read -r cmd > /dev/null
case "$cmd" in
"q") exitclient 0 ;;
"l") end_session ;;
"r") rm -f "$working_dir/iron-messagedata" && ${0} && exitclient 0 ;;
"reset") rm -rf "${working_dir:?}/" && ${0} && exitclient 0 ;;
"e") touch "$working_dir/iron-editor-message" ; $EDITOR "$working_dir/iron-editor-message" && ed=editor && sendmsg ;;
esac
$0 && exitclient 0
}
# ZZ/ZQ actions
zbind() {
read -r -n 1 curmode
# check mode
case "$curmode" in
"") curmode="NORMAL" && checkupt ;; # check for update, no mode change or keybind
"Q") exitclient 0 ;;
"Z") end_session ;;
esac
return
}
check_mode() {
while true; do
read -r -t 1 -n 1 curmode > /dev/null
case "$curmode" in
"i") curmode="INSERT" && insert ;;
"") curmode="NORMAL"; do_not_reparse=true; clear; printheader; parse_msgs ;;
"r") rm -f "$working_dir/iron-messagedata" "$working_dir/iron-message" && $0 && exitclient 0 ;;
":") cmd ;;
"e") touch "$working_dir/iron-editor-message" ; $EDITOR "$working_dir/iron-editor-message" && ed=editor && sendmsg ;;
"Z") zbind ;;
"l") end_session ;;
esac
checkupt || break
done
}
main "$@"