Something went wrong. Try again.
📦➔🦋 Store and retrieve files on the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071#!/usr/bin/env bash
function atfile.util.build_blob_uri() { did="$1" cid="$2" pds="$_server"
echo "$_fmt_blob_url" | sed -e "s|\[pds\]|$pds|g" -e "s|\[server\]|$pds|g" -e "s|\[cid\]|$cid|g" -e "s|\[did\]|$did|g"}
function atfile.util.build_out_filename() { key="$1" name="$2"
# shellcheck disable=SC2154 echo "$_fmt_out_file" | sed -e "s|\[name\]|$name|g" -e "s|\[key\]|$key|g"}
function atfile.util.build_query_array() { key="$1" values="$2"
unset query
if [[ -n "$values" ]]; then while IFS=$";" read -ra values_array; do for value in "${values_array[@]}"; do query+="$key=$value&" done done <<< "$values" fi
echo "$query"}
function atfile.util.check_prog() { command="$1" download_hint="$2" skip_hint="$3"
atfile.say.debug "Checking program '$1' exists..." if ! [ -x "$(command -v "$command")" ]; then message="'$command' not installed" if [[ -n "$download_hint" ]]; then if [[ "$download_hint" == "http"* ]]; then message="$message (download: $download_hint)" else message="$message (install: \`$download_hint\`)" fi fi
if [[ -n "$skip_hint" ]]; then message="$message\n↳ This is optional; set ${skip_hint}=1 to ignore" fi atfile.die "$message" fi}
function atfile.util.check_prog_gpg() { atfile.util.check_prog "gpg" "https://gnupg.org/download"}
function atfile.util.check_prog_optional_metadata() { # shellcheck disable=SC2154 [[ $_disable_ni_exiftool == 0 ]] && atfile.util.check_prog "exiftool" "https://exiftool.org" "${_envvar_prefix}_DISABLE_NI_EXIFTOOL" # shellcheck disable=SC2154 [[ $_disable_ni_mediainfo == 0 ]] && atfile.util.check_prog "mediainfo" "https://mediaarea.net/en/MediaInfo" "${_envvar_prefix}_DISABLE_NI_MEDIAINFO"}
function atfile.util.create_dir() { dir="$1"
atfile.say.debug "Creating directory '$dir'..."
if ! [[ -d $dir ]]; then mkdir -p "$dir" # shellcheck disable=SC2181 [[ $? != 0 ]] && atfile.die "Unable to create directory '$dir'" fi}
function atfile.util.fmt_int() { printf "%'d\n" "$1"}
function atfile.util.get_cache_path() { # shellcheck disable=SC2154 mkdir -p "$_path_cache" # shellcheck disable=SC2154 echo "$_path_cache/$1"}
function atfile.util.get_cdn_uri() { did="$1" blob_cid="$2" type="$3" cdn_uri="" case $type in "image/jpeg"|"image/png") cdn_uri="https://cdn.bsky.app/img/feed_thumbnail/plain/$did/$blob_cid@jpeg" ;; esac
echo "$cdn_uri"}
function atfile.util.get_ci() { [[ -d "/tangled/workspace" ]] && echo "tangled"}
# TODO: Support BusyBox's shit `date` command# `date -u +"$format" -s "1996-08-11 01:23:34"`function atfile.util.get_date() { date="$1" format="$2" unset in_format
[[ -z $format ]] && format="%Y-%m-%dT%H:%M:%SZ"
if [[ $date =~ ^([0-9]{4}-[0-9]{2}-[0-9]{2})T([0-9]{2}:[0-9]{2}:[0-9]{2}([.][0-9]{3}){0,1})Z$ ]]; then if [[ $_os == "bsd" ]]; then date="${BASH_REMATCH[1]} ${BASH_REMATCH[2]}" in_format="%Y-%m-%d %H:%M:%S" elif [[ $_os == "linux-musl" || $_os == "solaris" ]]; then date="${BASH_REMATCH[1]} ${BASH_REMATCH[2]}" fi fi [[ -z $in_format ]] && in_format="$format"
if [[ -z "$date" ]]; then if [[ $_os == "linux-musl" || $_os == "solaris" ]]; then echo "" else date -u +"$format" fi else if [[ $_os == "linux-musl" || $_os == "solaris" ]]; then date -u -d "$date" elif [[ $_os == "bsd" || $_os == "macos" ]]; then date -u -j -f "$in_format" "$date" +"$format" else date --date "$date" -u +"$format" fi fi}
function atfile.util.get_date_json() { date="$1" parsed="$2"
if [[ -z "$parsed" ]]; then if [[ -n "$date" ]]; then parsed_date="$(atfile.util.get_date "$date" 2> /dev/null)" # shellcheck disable=SC2181 [[ $? == 0 ]] && parsed="$parsed_date" fi fi
if [[ -n "$parsed" ]]; then echo "\"$parsed\"" else echo "null" fi}
function atfile.util.get_didplc_doc() { actor="$1"
function atfile.util.get_didplc_doc.request_doc() { endpoint="$1" actor="$2"
curl -H "User-Agent: $(atfile.util.get_uas)" -s -L -X GET "$endpoint/$actor" }
# shellcheck disable=SC2154 didplc_endpoint="$_endpoint_plc_directory" didplc_doc="$(atfile.util.get_didplc_doc.request_doc "$didplc_endpoint" "$actor")"
if [[ "$didplc_doc" != "{"* ]]; then # shellcheck disable=SC2154 didplc_endpoint="https://plc.directory" didplc_doc="$(atfile.util.get_didplc_doc.request_doc "$didplc_endpoint" "$actor")" fi
echo "$didplc_doc" | jq ". += {\"directory\": \"$didplc_endpoint\"}"}
function atfile.util.get_didweb_doc_url() { actor="$1" echo "https://${actor//did:web:/}/.well-known/did.json"}
function atfile.util.get_envvar() { envvar="$1" default="$2" envvar_from_envfile="$(atfile.util.get_envvar_from_envfile "$envvar")" envvar_value="" if [[ -n "${!envvar}" ]]; then envvar_value="${!envvar}" elif [[ -n "$envvar_from_envfile" ]]; then envvar_value="$envvar_from_envfile" fi if [[ -z "$envvar_value" ]]; then envvar_value="$default" fi echo "$envvar_value"}
function atfile.util.get_envvar_from_envfile() { variable="$1" [[ -f $_path_envvar ]] && atfile.util.get_var_from_file "$_path_envvar" "$variable"}
function atfile.util.get_exiftool_field() { file="$1" tag="$2" default="$3" output="" exiftool_output="$(eval "exiftool -c \"%+.6f\" -s -T -$tag \"$file\"")" if [[ -n "$exiftool_output" ]]; then if [[ "$exiftool_output" == "-" ]]; then output="$default" else output="$exiftool_output" fi else output="$default" fi echo "$(echo "$output" | sed "s|\"|\\\\\"|g")"}
function atfile.util.get_file_name_pretty() { file_record="$1" emoji="$(atfile.util.get_file_type_emoji "$(echo "$file_record" | jq -r '.file.mimeType')")" file_name_no_ext="$(echo "$file_record" | jq -r ".file.name" | cut -d "." -f 1)" output="$file_name_no_ext" meta_type="$(echo "$file_record" | jq -r ".meta.\"\$type\"")" if [[ -n "$meta_type" ]]; then case $meta_type in "$_nsid_meta#audio") album="$(echo "$file_record" | jq -r ".meta.tags.album")" album_artist="$(echo "$file_record" | jq -r ".meta.tags.album_artist")" date="$(echo "$file_record" | jq -r ".meta.tags.date")" disc="$(echo "$file_record" | jq -r ".meta.tags.disc.position")" title="$(echo "$file_record" | jq -r ".meta.tags.title")" track="$(echo "$file_record" | jq -r ".meta.tags.track.position")" [[ $(atfile.util.is_null_or_empty "$album") == 1 ]] && album="(Unknown Album)" [[ $(atfile.util.is_null_or_empty "$album_artist") == 1 ]] && album_artist="(Unknown Artist)" [[ $(atfile.util.is_null_or_empty "$disc") == 1 ]] && disc=0 [[ $(atfile.util.is_null_or_empty "$title") == 1 ]] && title="$file_name_no_ext" [[ $(atfile.util.is_null_or_empty "$track") == 1 ]] && track=0 output="$title\n $album_artist — $album" [[ $(atfile.util.is_null_or_empty "$date") == 0 ]] && output+=" ($(atfile.util.get_date "$date" "%Y"))" [[ $disc != 0 || $track != 0 ]] && output+=" [$disc.$track]" ;; "$_nsid_meta#photo") date="$(echo "$file_record" | jq -r ".meta.date.create")" lat="$(echo "$file_record" | jq -r ".meta.gps.lat")" long="$(echo "$file_record" | jq -r ".meta.gps.long")" title="$(echo "$file_record" | jq -r ".meta.title")" [[ -z "$title" ]] && title="$file_name_no_ext" output="$title" if [[ $(atfile.util.is_null_or_empty "$lat") == 0 && $(atfile.util.is_null_or_empty "$long") == 0 ]]; then output+="\n $long $lat" if [[ $(atfile.util.is_null_or_empty "$date") == 0 ]]; then output+=" — $(atfile.util.get_date "$date")" fi fi ;; "$_nsid_meta#video") title="$(echo "$file_record" | jq -r ".meta.tags.title")" [[ $(atfile.util.is_null_or_empty "$title") == 1 ]] && title="$file_name_no_ext" output="$title" ;; esac fi # BUG: Haiku Terminal has issues with emojis if [[ $_os != "haiku" ]]; then output="$emoji $output" fi output_last_line="$(echo -e "$output" | tail -n1)" output_last_line_length="${#output_last_line}" echo -e "$output" echo -e "$(atfile.util.repeat_char "-" "$output_last_line_length")"}
function atfile.util.get_file_size_pretty() { size="$1" suffix="" if (( size >= 1048576 )); then size=$(( size / 1048576 )) suffix="MiB" elif (( size >= 1024 )); then size=$(( size / 1024 )) suffix="KiB" else suffix="B" fi echo "$size $suffix"}
# NOTE: There is currently no API for getting the filesize limit on the serverfunction atfile.util.get_file_size_surplus_for_pds() { size="$1" pds="$2"
unset max_filesize
case $pds in *".host.bsky.network") max_filesize=1073741824 ;; esac
if [[ -z $max_filesize ]] || [[ $max_filesize == 0 ]] || (( size < max_filesize )); then echo 0 else echo $(( size - max_filesize )) fi}
function atfile.util.get_file_type_emoji() { mime_type="$1" short_type="$(echo "$mime_type" | cut -d "/" -f 1)" desc_type="$(echo "$mime_type" | cut -d "/" -f 2)"
case $short_type in "application") case "$desc_type" in # Apps (Desktop) "vnd.debian.binary-package"| \ "vnd.microsoft.portable-executable"| \ "x-executable"| \ "x-rpm") echo "💻" ;; # Apps (Mobile) "vnd.android.package-archive"| \ "x-ios-app") echo "📱" ;; # Archives "prs.atfile.car"| \ "gzip"|"x-7z-compressed"|"x-apple-diskimage"|"x-bzip2"|"x-stuffit"|"x-xz"|"zip") echo "📦" ;; # Disk Images "x-iso9660-image") echo "💿" ;; # Encrypted "prs.atfile.gpg-crypt") echo "🔑" ;; # Rich Text "pdf"| \ "vnd.oasis.opendocument.text") echo "📄" ;; *) echo "⚙️ " ;; esac ;; "audio") echo "🎵" ;; "font") echo "✏️" ;; "image") echo "🖼️ " ;; "inode") echo "🔌" ;; "text") case "$mime_type" in "text/x-shellscript") echo "⚙️ " ;; *) echo "📄" ;; esac ;; "video") echo "📼" ;; *) echo "❓" ;; esac}
function atfile.util.get_int_suffix() { int="$1" singular="$2" plural="$3"
[[ $int == 1 ]] && echo -e "$singular" || echo -e "$plural"}
function atfile.util.get_finger_record() { fingerprint_override="$1" unset enable_fingerprint_original
if [[ $fingerprint_override ]]; then enable_fingerprint_original="$_enable_fingerprint" _enable_fingerprint="$fingerprint_override" fi
echo -e "$(blue.zio.atfile.finger__machine)"
if [[ -n $enable_fingerprint_original ]]; then _enable_fingerprint="$enable_fingerprint_original" fi}
function atfile.util.get_line() { input="$1" index=$(( $2 + 1 )) echo -e "$input" | sed -n "$(( index ))"p}
function atfile.util.get_root_domain() { local url="$1" local domain=${url#*://} domain=${domain%%/*}
main_part=$(echo "$domain" | tr '.' '\n' | tac | sed -n '2p')
echo "$main_part"}
function atfile.util.get_mediainfo_field() { file="$1" category="$2" field="$3" default="$4" output=""
mediainfo_output="$(mediainfo --Inform="$category;%$field%\n" "$file")"
if [[ -n "$mediainfo_output" ]]; then if [[ "$mediainfo_output" == "None" ]]; then output="$default" else output="$mediainfo_output" fi else output="$default" fi echo "$(echo "$output" | sed "s|\"|\\\\\"|g")"}
function atfile.util.get_mediainfo_audio_json() { file="$1"
bitRates=$(atfile.util.get_mediainfo_field "$file" "Audio" "BitRate" 0) bitRate_modes=$(atfile.util.get_mediainfo_field "$file" "Audio" "BitRate_Mode" "") channelss=$(atfile.util.get_mediainfo_field "$file" "Audio" "Channels" 0) compressions="$(atfile.util.get_mediainfo_field "$file" "Audio" "Compression_Mode" "")" durations=$(atfile.util.get_mediainfo_field "$file" "Audio" "Duration" 0) formats="$(atfile.util.get_mediainfo_field "$file" "Audio" "Format" "")" format_ids="$(atfile.util.get_mediainfo_field "$file" "Audio" "CodecID" "")" format_profiles="$(atfile.util.get_mediainfo_field "$file" "Audio" "Format_Profile" "")" samplings=$(atfile.util.get_mediainfo_field "$file" "Audio" "SamplingRate" 0) titles="$(atfile.util.get_mediainfo_field "$file" "Audio" "Title" "")" lines="$(echo "$bitRates" | wc -l)" output=""
for (( i = 0 ; i < lines ; i++ )); do lossy=true [[ $(atfile.util.get_line "$compressions" $i) == "Lossless" ]] && lossy=false output+="{ \"bitRate\": $(atfile.util.get_line "$bitRates" $i), \"channels\": $(atfile.util.get_line "$channelss" $i), \"duration\": $(atfile.util.get_line "$durations" $i), \"format\": { \"id\": \"$(atfile.util.get_line "$format_ids" $i)\", \"name\": \"$(atfile.util.get_line "$formats" $i)\", \"profile\": \"$(atfile.util.get_line "$format_profiles" $i)\" }, \"mode\": \"$(atfile.util.get_line "$bitRate_modes" $i)\", \"lossy\": $lossy, \"sampling\": $(atfile.util.get_line "$samplings" $i), \"title\": \"$(atfile.util.get_line "$titles" $i)\"}," done echo "${output::-1}"}
function atfile.util.get_mediainfo_video_json() { file="$1"
bitRates=$(atfile.util.get_mediainfo_field "$file" "Video" "BitRate" 0) dim_height=$(atfile.util.get_mediainfo_field "$file" "Video" "Height" 0) dim_width=$(atfile.util.get_mediainfo_field "$file" "Video" "Width" 0) durations=$(atfile.util.get_mediainfo_field "$file" "Video" "Duration" 0) formats="$(atfile.util.get_mediainfo_field "$file" "Video" "Format" "")" format_ids="$(atfile.util.get_mediainfo_field "$file" "Video" "CodecID" "")" format_profiles="$(atfile.util.get_mediainfo_field "$file" "Video" "Format_Profile" "")" frameRates="$(atfile.util.get_mediainfo_field "$file" "Video" "FrameRate" "")" frameRate_modes="$(atfile.util.get_mediainfo_field "$file" "Video" "FrameRate_Mode" "")" titles="$(atfile.util.get_mediainfo_field "$file" "Video" "Title" "")" lines="$(echo "$bitRates" | wc -l)" output=""
for ((i = 0 ; i < lines ; i++ )); do output+="{ \"bitRate\": $(atfile.util.get_line "$bitRates" $i), \"dimensions\": { \"height\": $dim_height, \"width\": $dim_width }, \"duration\": $(atfile.util.get_line "$durations" $i), \"format\": { \"id\": \"$(atfile.util.get_line "$format_ids" $i)\", \"name\": \"$(atfile.util.get_line "$formats" $i)\", \"profile\": \"$(atfile.util.get_line "$format_profiles" $i)\" }, \"frameRate\": $(atfile.util.get_line "$frameRates" $i), \"mode\": \"$(atfile.util.get_line "$frameRate_modes" $i)\", \"title\": \"$(atfile.util.get_line "$titles" $i)\"}," done echo "${output::-1}"}
function atfile.util.get_meta_record() { file="$1" type="$2" case "$type" in "audio/"*) blue.zio.atfile.meta__audio "$1" ;; "image/"*) blue.zio.atfile.meta__photo "$1" ;; "video/"*) blue.zio.atfile.meta__video "$1" ;; *) blue.zio.atfile.meta__unknown "" "$type" ;; esac}
function atfile.util.get_md5() { file="$1"
unset checksum type="none" if [ -x "$(command -v md5sum)" ]; then hash="$(md5sum "$file" | cut -f 1 -d " ")" if [[ ${#hash} == 32 ]]; then checksum="$hash" type="md5" fi fi
echo "$checksum|$type"}
function atfile.util.get_os() { os="${OSTYPE,,}"
[[ -n $_force_os ]] && os="force-${_force_os,,}"
# shellcheck disable=SC2221 # shellcheck disable=SC2222 case $os in # BSD "freebsd"*|"netbsd"*|"openbsd"*|*"bsd"|"force-bsd") echo "bsd" ;; # Haiku "haiku"|"force-haiku") echo "haiku" ;; # Linux "linux-gnu"|"force-linux") echo "linux" ;; "cygwin"|"msys"|"force-linux-mingw") echo "linux-mingw" ;; "linux-musl"|"force-linux-musl") echo "linux-musl" ;; "linux-android"|"force-linux-termux") echo "linux-termux" ;; # macOS "darwin"*|"force-macos") echo "macos" ;; # SerenityOS "serenity"*|"force-serenity") echo "serenity" ;; # Solaris "solaris"*|"force-solaris") echo "solaris" ;; # Unknown *) echo "unknown-${os//force-/}" ;; esac}
function atfile.util.get_pds_pretty() { pds="$1"
pds_host="$(atfile.util.get_uri_segment "$pds" host)" unset pds_name unset pds_emoji
case "$pds_host" in *".host.bsky.network") bsky_host="$(echo "$pds_host" | cut -d "." -f 1)" bsky_region="$(echo "$pds_host" | cut -d "." -f 2)"
pds_name="${bsky_host^} ($(atfile.util.get_region_pretty "$bsky_region"))" pds_emoji="🍄" ;; "at.app.wafrn.net") pds_name="Wafrn"; pds_emoji="🌸" ;; "atproto.brid.gy") pds_name="Bridgy Fed"; pds_emoji="🔀" ;; "blacksky.app") pds_name="Blacksky"; pds_emoji="⬛" ;; "pds.sprk.so") pds_name="Spark"; pds_emoji="✨" ;; "tngl.sh") pds_name="Tangled"; pds_emoji="🪢" ;; *) pds_oauth_url="$pds/oauth/authorize" pds_oauth_page="$(curl -H "User-Agent: $(atfile.util.get_uas)" -s -L -X GET "$pds_oauth_url" | tr -d '\n')" pds_customization_data="$(echo "$pds_oauth_page" | sed -n 's/.*window\["__customizationData"\]=JSON.parse("\(.*\)");.*/\1/p' | sed 's/\\"/"/g; s/\\\\/\\/g' | sed 's/");window\[".*$//')"
if [[ $pds_customization_data == "{"* ]]; then pds_name="$(echo "$pds_customization_data" | jq -r '.name')" pds_emoji="🟦" else pds_name="$pds_host" fi ;; esac
# BUG: Haiku Terminal has issues with emojis if [[ -n "$pds_emoji" ]] && [[ $_os != "haiku" ]]; then echo "$pds_emoji $pds_name" else echo "$pds_name" fi}
function atfile.util.get_random() { amount="$1" [[ -z "$amount" ]] && amount="6" echo "$(tr -dc A-Za-z0-9 </dev/urandom | head -c "$amount"; echo)"}
function atfile.util.get_random_pbc_jetstream() { pbc_jetstreams=( "jetstream1.us-east" "jetstream2.us-east" "jetstream1.us-west" "jetstream2.us-east" )
pbc_jetstream="${pbc_jetstreams[ $RANDOM % ${#pbc_jetstreams[@]} ]}"
echo "wss://$pbc_jetstream.bsky.network"}
function atfile.util.get_realpath() { path="$1"
if [[ $_os == "solaris" ]]; then # SEE: https://stackoverflow.com/a/11554895 # INVESTIGATE: Use this for every OS? # shellcheck disable=SC2015 [ -d "$path" ] && ( # shellcheck disable=SC1007 cd_path= \cd "$1" /bin/pwd ) || ( # shellcheck disable=SC1007 cd_path= \cd "$(dirname "$1")" && printf "%s/%s\n" "$(/bin/pwd)" "$(basename "$1")" ) else realpath "$path" fi}
function atfile.util.get_region_pretty() { region="$1"
region_sub="$(echo "$region" | cut -d "-" -f 2)" region="$(echo "$region" | cut -d "-" -f 1)"
echo "${region^^} ${region_sub^}"}
function atfile.util.get_rkey_from_at_uri() { at_uri="$1" echo "$at_uri" | cut -d "/" -f 5}
function atfile.util.get_seconds_since_start() { current="$(atfile.util.get_date "" "%s")" # shellcheck disable=SC2154 echo "$(( current - _start ))"}
function atfile.util.get_term_cols() { unset rows if [ -x "$(command -v tput)" ]; then cols=$(tput cols) fi
if [[ -n $cols ]]; then echo "$cols" else echo 80 fi}
function atfile.util.get_term_rows() { unset rows if [ -x "$(command -v tput)" ]; then rows=$(tput lines) fi
if [[ -n $rows ]]; then echo "$rows" else echo 30 fi}
function atfile.util.get_var_from_file() { file="$1"
if [[ -f "$file" ]]; then variable="$2" found_line="$(grep "\b${variable}=" "$file")" if [[ -n "$found_line" ]] && [[ ! "$found_line" == \#* ]]; then output="${found_line#"${variable}"=}" output="${output%\"}" output="${output#\"}"
if [[ $output == *"\$("* && $output == *")"* ]]; then eval "echo \"$output\"" else echo "$output" fi fi fi}
function atfile.util.get_uas() { # shellcheck disable=SC2154 echo "$_name/$_version"}
function atfile.util.get_xrpc_error() { exit_code="$1" data="$2"
if [[ $exit_code != 0 || -z "$data" || "$data" == "null" || "$data" == "{}" || "$data" == *"\"error\":"* ]]; then if [[ "$data" == "{"* && "$data" == *"\"error\":"* ]]; then error="$(echo "$data" | jq -r ".error")" message="$(echo "$data" | jq -r ".message")"
if [[ -z $message ]]; then echo "$error" else echo "[$error] $message" fi else echo "?" fi else echo "" fi}
function atfile.util.get_yn() { yn="$1" # shellcheck disable=SC2154 if [[ $_output_json == 0 ]]; then if [[ $yn == 0 ]]; then echo "No" else echo "Yes" fi else if [[ $yn == 0 ]]; then echo "false" else echo "true" fi fi}
function atfile.util.is_null_or_empty() { if [[ -z "$1" ]] || [[ "$1" == null ]]; then echo 1 else echo 0 fi}
function atfile.util.is_url_accessible_in_browser() { url="$1" # shellcheck disable=SC2154 atfile.util.is_url_okay "$url" "$_test_desktop_uas"}
function atfile.util.is_url_okay() { url="$1" uas="$2"
[[ -z "$uas" ]] && uas="$(atfile.util.get_uas)"
code="$(curl -H "User-Agent: $uas" -s -o /dev/null -w "%{http_code}" "$url")"
if [[ "$code" == 2* || "$code" == 3* ]]; then echo 1 else echo 0 fi}
function atfile.util.launch_uri() { uri="$1"
if [[ -n $DISPLAY ]] && [ -x "$(command -v xdg-open)" ]; then xdg-open "$uri" else case $_os in "haiku") open "$uri" ;; "macos") open "$uri" ;; *) echo "$uri" esac fi}
function atfile.util.parse_at_uri() { at_uri="$1" segment="$2" unset parsed_at_uri
case $segment in "actor"|"did"|"handle") echo "$at_uri" | cut -d "/" -f 3 ;; "collection") echo "$at_uri" | cut -d "/" -f 4 ;; "key"|"rkey") echo "$at_uri" | cut -d "/" -f 5 ;; *) echo "$at_uri" | cut -d "/" -f "$segment" ;; esac}
function atfile.util.get_uri_segment() { uri="$1" segment="$2" unset parsed_uri
case $segment in "host") echo "$uri" | cut -d "/" -f 3 ;; "protocol") echo "$uri" | cut -d ":" -f 1 ;; *) echo "$uri" | cut -d "/" -f "$segment" ;; esac}
# HACK: This essentially breaks the entire session (it overrides $_username and# $_server). If sourcing, use atfile.util.override_actor_reset() to# resetfunction atfile.util.override_actor() { actor="$1" [[ -z "$_server_original" ]] && _server_original="$_server" [[ -z "$_username_original" ]] && _username_original="$_username" [[ -z "$_fmt_blob_url_original" ]] && _fmt_blob_url_original="$_fmt_blob_url" resolved_did="$(atfile.util.resolve_identity "$actor")" error="$(atfile.util.get_xrpc_error $? "$resolved_did")" [[ -n "$error" ]] && atfile.die.xrpc_error "Unable to resolve '$actor'" "$resolved_did"
_username="$(echo "$resolved_did" | cut -d "|" -f 1)" _server="$(echo "$resolved_did" | cut -d "|" -f 2)" _fmt_blob_url="$_fmt_blob_url_default"
atfile.say.debug "Overridden identity\n↳ DID: $_username\n↳ PDS: $_server\n↳ Blob URL: $_fmt_blob_url"
atfile.say.debug "Deleting cached JWT for '$_username_original'..." atfile.cache.del "token"}
# NOTE: This is to help during sourcing if atfile.uitl.override_actor() has# been calledfunction atfile.util.override_actor_reset() { [[ -n "$_server_original" ]] && _server="$_server_original"; unset _server_original [[ -n "$_username_original" ]] && _username="$_username_original"; unset _username_original [[ -n "$_fmt_blob_url_original" ]] && _fmt_blob_url="$_fmt_blob_url_original"; unset _fmt_blob_url_original}
function atfile.util.parse_exiftool_date() { in_date="$1" tz="$2" date="$(echo "$in_date" | cut -d " " -f 1 | sed -e "s|:|-|g")" time="$(echo "$in_date" | cut -d " " -f 2)" echo "$date $time $tz"}
function atfile.util.parse_version() { version="$1" version="$(echo "$version" | cut -d "+" -f 1)" v_major="$(printf "%04d\n" "$(echo "$version" | cut -d "." -f 1)")" v_minor="$(printf "%04d\n" "$(echo "$version" | cut -d "." -f 2)")" v_rev="$(printf "%04d\n" "$(echo "$version" | cut -d "." -f 3)")" echo "${v_major}${v_minor}${v_rev}" | sed 's/^0*//'}
function atfile.util.print_blob_url_output() { blob_uri="$1" run_cmd="$_prog url $key" [[ -n "$_username_original" ]] && run_cmd+=" $_username" if [[ $(atfile.util.is_url_accessible_in_browser "$blob_uri") == 0 ]]; then echo -e "↳ Blob: ⚠️ Blob cannot be viewed in a browser\n Run '$run_cmd' to get URL" else echo -e "↳ Blob: $blob_uri" fi}
function atfile.util.print_override_envvar_debug() { pretty_name="$1" override_variable="$2" override_value="$(eval echo "\$_force${override_variable}")" envvar_suffix="_FORCE${override_variable^^}"
atfile.say.debug "Overriding $pretty_name (\$$override_variable)\n↳ ${_envvar_prefix}$envvar_suffix set to '$override_value'"}
function atfile.util.print_seconds_since_start_debug() { seconds=$(atfile.util.get_seconds_since_start) second_unit="$(atfile.util.get_int_suffix "$seconds" "second" "seconds")"
atfile.say.debug "$seconds $second_unit since start"}
function atfile.util.print_table_paginate_hint() { cursor="$1" count="$2" # shellcheck disable=SC2154 if [[ -z $count ]] || (( ( count + _max_list_buffer ) >= _max_list )); then first_line="List is limited to $_max_list results. To print more results," first_line_length=$(( ${#first_line} + 3 )) # shellcheck disable=SC2154 echo -e "$(atfile.util.repeat_char "-" $first_line_length)\nℹ️ $first_line\n run \`$_prog $_command $cursor\`" fi}
function atfile.util.repeat_char() { char="$1" amount="$2" if [ -x "$(command -v seq)" ]; then printf "%0.s$char" $(seq 1 "$amount") else echo "$char" fi}
function atfile.util.resolve_identity() { actor="$1" if [[ "$actor" != "did:"* ]]; then # shellcheck disable=SC2154 resolved_handle="$(atfile.xrpc.bsky.get "com.atproto.identity.resolveHandle" "handle=$actor")" error="$(atfile.util.get_xrpc_error $? "$resolved_handle")"
if [[ -z "$error" ]]; then actor="$(echo "$resolved_handle" | jq -r ".did")" fi fi if [[ "$actor" == "did:"* ]]; then unset did_doc
case "$actor" in "did:plc:"*) did_doc="$(atfile.util.get_didplc_doc "$actor")" ;; "did:web:"*) did_doc="$(curl -H "User-Agent: $(atfile.util.get_uas)" -s -L -X GET "$(atfile.util.get_didweb_doc_url "$actor")")" ;; *) echo "Unknown DID type 'did:$(echo "$actor" | cut -d ":" -f 2)'"; exit 255;; esac
if [[ -n "$did_doc" ]]; then did="$(echo "$did_doc" | jq -r ".id")"
if [[ $(atfile.util.is_null_or_empty "$did") == 1 ]]; then echo "$error" exit 255 fi
unset aliases unset handle didplc_dir="$(echo "$did_doc" | jq -r ".directory")" pds="$(echo "$did_doc" | jq -r '.service[] | select(.id == "#atproto_pds") | .serviceEndpoint')"
while IFS=$'\n' read -r a; do aliases+="$a;"
if [[ -z $handle && "$a" == "at://"* && "$a" != "at://did:"* ]]; then handle="$a" fi done <<< "$(echo "$did_doc" | jq -r '.alsoKnownAs[]')"
[[ $didplc_dir == "null" ]] && unset didplc_dir [[ -z "$handle" ]] && handle="invalid.handle" echo "$did|$pds|$handle|$didplc_dir|$aliases" fi else echo "$error" exit 255 fi}
function atfile.util.set_token_cache() { did="$1" access="$2" refresh="$3"
atfile.cache.set "token" "$did|$access|$refresh"}
function atfile.util.get_token_cache() { part="$1" unset index
token_cache="$(atfile.cache.get "token")"
if [[ -n "$token_cache" ]]; then case "$part" in "did"|"username") index="1" ;; "access") index="2" ;; "refresh") index="3" ;; esac
echo "$token_cache" | cut -d "|" -f $index fi}
function atfile.util.source_hook() { file="$1"
if [[ -n "$file" ]]; then atfile.say.debug "Sourcing: $file" if [[ -f "$file" ]]; then # shellcheck disable=SC1090 . "$file" else atfile.die "Unable to source '$file'" fi fi}
function atfile.util.write_cache() { file="$1" file_path="$_path_cache/$1" content="$2" atfile.util.get_cache "$file" echo -e "$content" > "$file_path" # shellcheck disable=SC2320 # shellcheck disable=SC2181 [[ $? != 0 ]] && atfile.die "Unable to write to cache file ($file)"}