📦➔🦋 Store and retrieve files on the ATmosphere
at dev 31 kB view raw
1#!/usr/bin/env bash 2 3function atfile.util.build_blob_uri() { 4 did="$1" 5 cid="$2" 6 pds="$_server" 7 8 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" 9} 10 11function atfile.util.build_out_filename() { 12 key="$1" 13 name="$2" 14 15 # shellcheck disable=SC2154 16 echo "$_fmt_out_file" | sed -e "s|\[name\]|$name|g" -e "s|\[key\]|$key|g" 17} 18 19function atfile.util.build_query_array() { 20 key="$1" 21 values="$2" 22 23 unset query 24 25 if [[ -n "$values" ]]; then 26 while IFS=$";" read -ra values_array; do 27 for value in "${values_array[@]}"; do 28 query+="$key=$value&" 29 done 30 done <<< "$values" 31 fi 32 33 echo "$query" 34} 35 36function atfile.util.check_prog() { 37 command="$1" 38 download_hint="$2" 39 skip_hint="$3" 40 41 atfile.say.debug "Checking program '$1' exists..." 42 43 if ! [ -x "$(command -v "$command")" ]; then 44 message="'$command' not installed" 45 46 if [[ -n "$download_hint" ]]; then 47 if [[ "$download_hint" == "http"* ]]; then 48 message="$message (download: $download_hint)" 49 else 50 message="$message (install: \`$download_hint\`)" 51 fi 52 fi 53 54 if [[ -n "$skip_hint" ]]; then 55 message="$message\n↳ This is optional; set ${skip_hint}=1 to ignore" 56 fi 57 58 atfile.die "$message" 59 fi 60} 61 62function atfile.util.check_prog_gpg() { 63 atfile.util.check_prog "gpg" "https://gnupg.org/download" 64} 65 66function atfile.util.check_prog_optional_metadata() { 67 # shellcheck disable=SC2154 68 [[ $_disable_ni_exiftool == 0 ]] && atfile.util.check_prog "exiftool" "https://exiftool.org" "${_envvar_prefix}_DISABLE_NI_EXIFTOOL" 69 # shellcheck disable=SC2154 70 [[ $_disable_ni_mediainfo == 0 ]] && atfile.util.check_prog "mediainfo" "https://mediaarea.net/en/MediaInfo" "${_envvar_prefix}_DISABLE_NI_MEDIAINFO" 71} 72 73function atfile.util.create_dir() { 74 dir="$1" 75 76 atfile.say.debug "Creating directory '$dir'..." 77 78 if ! [[ -d $dir ]]; then 79 mkdir -p "$dir" 80 # shellcheck disable=SC2181 81 [[ $? != 0 ]] && atfile.die "Unable to create directory '$dir'" 82 fi 83} 84 85function atfile.util.fmt_int() { 86 printf "%'d\n" "$1" 87} 88 89function atfile.util.get_cache_path() { 90 # shellcheck disable=SC2154 91 mkdir -p "$_path_cache" 92 # shellcheck disable=SC2154 93 echo "$_path_cache/$1" 94} 95 96function atfile.util.get_cdn_uri() { 97 did="$1" 98 blob_cid="$2" 99 type="$3" 100 101 cdn_uri="" 102 103 case $type in 104 "image/jpeg"|"image/png") cdn_uri="https://cdn.bsky.app/img/feed_thumbnail/plain/$did/$blob_cid@jpeg" ;; 105 esac 106 107 echo "$cdn_uri" 108} 109 110function atfile.util.get_ci() { 111 [[ -d "/tangled/workspace" ]] && echo "tangled" 112} 113 114# TODO: Support BusyBox's shit `date` command 115# `date -u +"$format" -s "1996-08-11 01:23:34"` 116function atfile.util.get_date() { 117 date="$1" 118 format="$2" 119 unset in_format 120 121 [[ -z $format ]] && format="%Y-%m-%dT%H:%M:%SZ" 122 123 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 124 if [[ $_os == "bsd" ]]; then 125 date="${BASH_REMATCH[1]} ${BASH_REMATCH[2]}" 126 in_format="%Y-%m-%d %H:%M:%S" 127 elif [[ $_os == "linux-musl" || $_os == "solaris" ]]; then 128 date="${BASH_REMATCH[1]} ${BASH_REMATCH[2]}" 129 fi 130 fi 131 132 [[ -z $in_format ]] && in_format="$format" 133 134 if [[ -z "$date" ]]; then 135 if [[ $_os == "linux-musl" || $_os == "solaris" ]]; then 136 echo "" 137 else 138 date -u +"$format" 139 fi 140 else 141 if [[ $_os == "linux-musl" || $_os == "solaris" ]]; then 142 date -u -d "$date" 143 elif [[ $_os == "bsd" || $_os == "macos" ]]; then 144 date -u -j -f "$in_format" "$date" +"$format" 145 else 146 date --date "$date" -u +"$format" 147 fi 148 fi 149} 150 151function atfile.util.get_date_json() { 152 date="$1" 153 parsed="$2" 154 155 if [[ -z "$parsed" ]]; then 156 if [[ -n "$date" ]]; then 157 parsed_date="$(atfile.util.get_date "$date" 2> /dev/null)" 158 # shellcheck disable=SC2181 159 [[ $? == 0 ]] && parsed="$parsed_date" 160 fi 161 fi 162 163 if [[ -n "$parsed" ]]; then 164 echo "\"$parsed\"" 165 else 166 echo "null" 167 fi 168} 169 170function atfile.util.get_didplc_doc() { 171 actor="$1" 172 173 function atfile.util.get_didplc_doc.request_doc() { 174 endpoint="$1" 175 actor="$2" 176 177 curl -H "User-Agent: $(atfile.util.get_uas)" -s -L -X GET "$endpoint/$actor" 178 } 179 180 # shellcheck disable=SC2154 181 didplc_endpoint="$_endpoint_plc_directory" 182 didplc_doc="$(atfile.util.get_didplc_doc.request_doc "$didplc_endpoint" "$actor")" 183 184 if [[ "$didplc_doc" != "{"* ]]; then 185 # shellcheck disable=SC2154 186 didplc_endpoint="https://plc.directory" 187 didplc_doc="$(atfile.util.get_didplc_doc.request_doc "$didplc_endpoint" "$actor")" 188 fi 189 190 echo "$didplc_doc" | jq ". += {\"directory\": \"$didplc_endpoint\"}" 191} 192 193function atfile.util.get_didweb_doc_url() { 194 actor="$1" 195 echo "https://${actor//did:web:/}/.well-known/did.json" 196} 197 198function atfile.util.get_envvar() { 199 envvar="$1" 200 default="$2" 201 envvar_from_envfile="$(atfile.util.get_envvar_from_envfile "$envvar")" 202 envvar_value="" 203 204 if [[ -n "${!envvar}" ]]; then 205 envvar_value="${!envvar}" 206 elif [[ -n "$envvar_from_envfile" ]]; then 207 envvar_value="$envvar_from_envfile" 208 fi 209 210 if [[ -z "$envvar_value" ]]; then 211 envvar_value="$default" 212 fi 213 214 echo "$envvar_value" 215} 216 217function atfile.util.get_envvar_from_envfile() { 218 variable="$1" 219 [[ -f $_path_envvar ]] && atfile.util.get_var_from_file "$_path_envvar" "$variable" 220} 221 222function atfile.util.get_exiftool_field() { 223 file="$1" 224 tag="$2" 225 default="$3" 226 output="" 227 228 exiftool_output="$(eval "exiftool -c \"%+.6f\" -s -T -$tag \"$file\"")" 229 230 if [[ -n "$exiftool_output" ]]; then 231 if [[ "$exiftool_output" == "-" ]]; then 232 output="$default" 233 else 234 output="$exiftool_output" 235 fi 236 else 237 output="$default" 238 fi 239 240 echo "$(echo "$output" | sed "s|\"|\\\\\"|g")" 241} 242 243function atfile.util.get_file_name_pretty() { 244 file_record="$1" 245 emoji="$(atfile.util.get_file_type_emoji "$(echo "$file_record" | jq -r '.file.mimeType')")" 246 file_name_no_ext="$(echo "$file_record" | jq -r ".file.name" | cut -d "." -f 1)" 247 output="$file_name_no_ext" 248 249 meta_type="$(echo "$file_record" | jq -r ".meta.\"\$type\"")" 250 251 if [[ -n "$meta_type" ]]; then 252 case $meta_type in 253 "$_nsid_meta#audio") 254 album="$(echo "$file_record" | jq -r ".meta.tags.album")" 255 album_artist="$(echo "$file_record" | jq -r ".meta.tags.album_artist")" 256 date="$(echo "$file_record" | jq -r ".meta.tags.date")" 257 disc="$(echo "$file_record" | jq -r ".meta.tags.disc.position")" 258 title="$(echo "$file_record" | jq -r ".meta.tags.title")" 259 track="$(echo "$file_record" | jq -r ".meta.tags.track.position")" 260 261 [[ $(atfile.util.is_null_or_empty "$album") == 1 ]] && album="(Unknown Album)" 262 [[ $(atfile.util.is_null_or_empty "$album_artist") == 1 ]] && album_artist="(Unknown Artist)" 263 [[ $(atfile.util.is_null_or_empty "$disc") == 1 ]] && disc=0 264 [[ $(atfile.util.is_null_or_empty "$title") == 1 ]] && title="$file_name_no_ext" 265 [[ $(atfile.util.is_null_or_empty "$track") == 1 ]] && track=0 266 267 output="$title\n $album_artist$album" 268 [[ $(atfile.util.is_null_or_empty "$date") == 0 ]] && output+=" ($(atfile.util.get_date "$date" "%Y"))" 269 [[ $disc != 0 || $track != 0 ]] && output+=" [$disc.$track]" 270 ;; 271 "$_nsid_meta#photo") 272 date="$(echo "$file_record" | jq -r ".meta.date.create")" 273 lat="$(echo "$file_record" | jq -r ".meta.gps.lat")" 274 long="$(echo "$file_record" | jq -r ".meta.gps.long")" 275 title="$(echo "$file_record" | jq -r ".meta.title")" 276 277 [[ -z "$title" ]] && title="$file_name_no_ext" 278 279 output="$title" 280 281 if [[ $(atfile.util.is_null_or_empty "$lat") == 0 && $(atfile.util.is_null_or_empty "$long") == 0 ]]; then 282 output+="\n $long $lat" 283 284 if [[ $(atfile.util.is_null_or_empty "$date") == 0 ]]; then 285 output+="$(atfile.util.get_date "$date")" 286 fi 287 fi 288 ;; 289 "$_nsid_meta#video") 290 title="$(echo "$file_record" | jq -r ".meta.tags.title")" 291 292 [[ $(atfile.util.is_null_or_empty "$title") == 1 ]] && title="$file_name_no_ext" 293 294 output="$title" 295 ;; 296 esac 297 fi 298 299 # BUG: Haiku Terminal has issues with emojis 300 if [[ $_os != "haiku" ]]; then 301 output="$emoji $output" 302 fi 303 304 output_last_line="$(echo -e "$output" | tail -n1)" 305 output_last_line_length="${#output_last_line}" 306 307 echo -e "$output" 308 echo -e "$(atfile.util.repeat_char "-" "$output_last_line_length")" 309} 310 311function atfile.util.get_file_size_pretty() { 312 size="$1" 313 suffix="" 314 315 if (( size >= 1048576 )); then 316 size=$(( size / 1048576 )) 317 suffix="MiB" 318 elif (( size >= 1024 )); then 319 size=$(( size / 1024 )) 320 suffix="KiB" 321 else 322 suffix="B" 323 fi 324 325 echo "$size $suffix" 326} 327 328# NOTE: There is currently no API for getting the filesize limit on the server 329function atfile.util.get_file_size_surplus_for_pds() { 330 size="$1" 331 pds="$2" 332 333 unset max_filesize 334 335 case $pds in 336 *".host.bsky.network") max_filesize=1073741824 ;; 337 esac 338 339 if [[ -z $max_filesize ]] || [[ $max_filesize == 0 ]] || (( size < max_filesize )); then 340 echo 0 341 else 342 echo $(( size - max_filesize )) 343 fi 344} 345 346function atfile.util.get_file_type_emoji() { 347 mime_type="$1" 348 short_type="$(echo "$mime_type" | cut -d "/" -f 1)" 349 desc_type="$(echo "$mime_type" | cut -d "/" -f 2)" 350 351 case $short_type in 352 "application") 353 case "$desc_type" in 354 # Apps (Desktop) 355 "vnd.debian.binary-package"| \ 356 "vnd.microsoft.portable-executable"| \ 357 "x-executable"| \ 358 "x-rpm") 359 echo "💻" ;; 360 # Apps (Mobile) 361 "vnd.android.package-archive"| \ 362 "x-ios-app") 363 echo "📱" ;; 364 # Archives 365 "prs.atfile.car"| \ 366 "gzip"|"x-7z-compressed"|"x-apple-diskimage"|"x-bzip2"|"x-stuffit"|"x-xz"|"zip") 367 echo "📦" ;; 368 # Disk Images 369 "x-iso9660-image") 370 echo "💿" ;; 371 # Encrypted 372 "prs.atfile.gpg-crypt") 373 echo "🔑" ;; 374 # Rich Text 375 "pdf"| \ 376 "vnd.oasis.opendocument.text") 377 echo "📄" ;; 378 *) echo "⚙️ " ;; 379 esac 380 ;; 381 "audio") echo "🎵" ;; 382 "font") echo "✏️" ;; 383 "image") echo "🖼️ " ;; 384 "inode") echo "🔌" ;; 385 "text") 386 case "$mime_type" in 387 "text/x-shellscript") echo "⚙️ " ;; 388 *) echo "📄" ;; 389 esac 390 ;; 391 "video") echo "📼" ;; 392 *) echo "❓" ;; 393 esac 394} 395 396function atfile.util.get_int_suffix() { 397 int="$1" 398 singular="$2" 399 plural="$3" 400 401 [[ $int == 1 ]] && echo -e "$singular" || echo -e "$plural" 402} 403 404function atfile.util.get_finger_record() { 405 fingerprint_override="$1" 406 unset enable_fingerprint_original 407 408 if [[ $fingerprint_override ]]; then 409 enable_fingerprint_original="$_enable_fingerprint" 410 _enable_fingerprint="$fingerprint_override" 411 fi 412 413 echo -e "$(blue.zio.atfile.finger__machine)" 414 415 if [[ -n $enable_fingerprint_original ]]; then 416 _enable_fingerprint="$enable_fingerprint_original" 417 fi 418} 419 420function atfile.util.get_line() { 421 input="$1" 422 index=$(( $2 + 1 )) 423 424 echo -e "$input" | sed -n "$(( index ))"p 425} 426 427function atfile.util.get_mediainfo_field() { 428 file="$1" 429 category="$2" 430 field="$3" 431 default="$4" 432 output="" 433 434 mediainfo_output="$(mediainfo --Inform="$category;%$field%\n" "$file")" 435 436 if [[ -n "$mediainfo_output" ]]; then 437 if [[ "$mediainfo_output" == "None" ]]; then 438 output="$default" 439 else 440 output="$mediainfo_output" 441 fi 442 else 443 output="$default" 444 fi 445 446 echo "$(echo "$output" | sed "s|\"|\\\\\"|g")" 447} 448 449function atfile.util.get_mediainfo_audio_json() { 450 file="$1" 451 452 bitRates=$(atfile.util.get_mediainfo_field "$file" "Audio" "BitRate" 0) 453 bitRate_modes=$(atfile.util.get_mediainfo_field "$file" "Audio" "BitRate_Mode" "") 454 channelss=$(atfile.util.get_mediainfo_field "$file" "Audio" "Channels" 0) 455 compressions="$(atfile.util.get_mediainfo_field "$file" "Audio" "Compression_Mode" "")" 456 durations=$(atfile.util.get_mediainfo_field "$file" "Audio" "Duration" 0) 457 formats="$(atfile.util.get_mediainfo_field "$file" "Audio" "Format" "")" 458 format_ids="$(atfile.util.get_mediainfo_field "$file" "Audio" "CodecID" "")" 459 format_profiles="$(atfile.util.get_mediainfo_field "$file" "Audio" "Format_Profile" "")" 460 samplings=$(atfile.util.get_mediainfo_field "$file" "Audio" "SamplingRate" 0) 461 titles="$(atfile.util.get_mediainfo_field "$file" "Audio" "Title" "")" 462 463 lines="$(echo "$bitRates" | wc -l)" 464 output="" 465 466 for (( i = 0 ; i < lines ; i++ )); do 467 lossy=true 468 469 [[ $(atfile.util.get_line "$compressions" $i) == "Lossless" ]] && lossy=false 470 471 output+="{ 472 \"bitRate\": $(atfile.util.get_line "$bitRates" $i), 473 \"channels\": $(atfile.util.get_line "$channelss" $i), 474 \"duration\": $(atfile.util.get_line "$durations" $i), 475 \"format\": { 476 \"id\": \"$(atfile.util.get_line "$format_ids" $i)\", 477 \"name\": \"$(atfile.util.get_line "$formats" $i)\", 478 \"profile\": \"$(atfile.util.get_line "$format_profiles" $i)\" 479 }, 480 \"mode\": \"$(atfile.util.get_line "$bitRate_modes" $i)\", 481 \"lossy\": $lossy, 482 \"sampling\": $(atfile.util.get_line "$samplings" $i), 483 \"title\": \"$(atfile.util.get_line "$titles" $i)\" 484}," 485 done 486 487 echo "${output::-1}" 488} 489 490function atfile.util.get_mediainfo_video_json() { 491 file="$1" 492 493 bitRates=$(atfile.util.get_mediainfo_field "$file" "Video" "BitRate" 0) 494 dim_height=$(atfile.util.get_mediainfo_field "$file" "Video" "Height" 0) 495 dim_width=$(atfile.util.get_mediainfo_field "$file" "Video" "Width" 0) 496 durations=$(atfile.util.get_mediainfo_field "$file" "Video" "Duration" 0) 497 formats="$(atfile.util.get_mediainfo_field "$file" "Video" "Format" "")" 498 format_ids="$(atfile.util.get_mediainfo_field "$file" "Video" "CodecID" "")" 499 format_profiles="$(atfile.util.get_mediainfo_field "$file" "Video" "Format_Profile" "")" 500 frameRates="$(atfile.util.get_mediainfo_field "$file" "Video" "FrameRate" "")" 501 frameRate_modes="$(atfile.util.get_mediainfo_field "$file" "Video" "FrameRate_Mode" "")" 502 titles="$(atfile.util.get_mediainfo_field "$file" "Video" "Title" "")" 503 504 lines="$(echo "$bitRates" | wc -l)" 505 output="" 506 507 for ((i = 0 ; i < lines ; i++ )); do 508 output+="{ 509 \"bitRate\": $(atfile.util.get_line "$bitRates" $i), 510 \"dimensions\": { 511 \"height\": $dim_height, 512 \"width\": $dim_width 513 }, 514 \"duration\": $(atfile.util.get_line "$durations" $i), 515 \"format\": { 516 \"id\": \"$(atfile.util.get_line "$format_ids" $i)\", 517 \"name\": \"$(atfile.util.get_line "$formats" $i)\", 518 \"profile\": \"$(atfile.util.get_line "$format_profiles" $i)\" 519 }, 520 \"frameRate\": $(atfile.util.get_line "$frameRates" $i), 521 \"mode\": \"$(atfile.util.get_line "$frameRate_modes" $i)\", 522 \"title\": \"$(atfile.util.get_line "$titles" $i)\" 523}," 524 done 525 526 echo "${output::-1}" 527} 528 529function atfile.util.get_meta_record() { 530 file="$1" 531 type="$2" 532 533 case "$type" in 534 "audio/"*) blue.zio.atfile.meta__audio "$1" ;; 535 "image/"*) blue.zio.atfile.meta__photo "$1" ;; 536 "video/"*) blue.zio.atfile.meta__video "$1" ;; 537 *) blue.zio.atfile.meta__unknown "" "$type" ;; 538 esac 539} 540 541function atfile.util.get_md5() { 542 file="$1" 543 544 unset checksum 545 type="none" 546 547 if [ -x "$(command -v md5sum)" ]; then 548 hash="$(md5sum "$file" | cut -f 1 -d " ")" 549 if [[ ${#hash} == 32 ]]; then 550 checksum="$hash" 551 type="md5" 552 fi 553 fi 554 555 echo "$checksum|$type" 556} 557 558function atfile.util.get_os() { 559 os="${OSTYPE,,}" 560 561 [[ -n $_force_os ]] && os="force-${_force_os,,}" 562 563 # shellcheck disable=SC2221 564 # shellcheck disable=SC2222 565 case $os in 566 # BSD 567 "freebsd"*|"netbsd"*|"openbsd"*|*"bsd"|"force-bsd") echo "bsd" ;; 568 # Haiku 569 "haiku"|"force-haiku") echo "haiku" ;; 570 # Linux 571 "linux-gnu"|"force-linux") echo "linux" ;; 572 "cygwin"|"msys"|"force-linux-mingw") echo "linux-mingw" ;; 573 "linux-musl"|"force-linux-musl") echo "linux-musl" ;; 574 "linux-android"|"force-linux-termux") echo "linux-termux" ;; 575 # macOS 576 "darwin"*|"force-macos") echo "macos" ;; 577 # SerenityOS 578 "serenity"*|"force-serenity") echo "serenity" ;; 579 # Solaris 580 "solaris"*|"force-solaris") echo "solaris" ;; 581 # Unknown 582 *) echo "unknown-${os//force-/}" ;; 583 esac 584} 585 586function atfile.util.get_pds_pretty() { 587 pds="$1" 588 589 pds_host="$(atfile.util.get_uri_segment "$pds" host)" 590 unset pds_name 591 unset pds_emoji 592 593 case "$pds_host" in 594 *".host.bsky.network") 595 bsky_host="$(echo "$pds_host" | cut -d "." -f 1)" 596 bsky_region="$(echo "$pds_host" | cut -d "." -f 2)" 597 598 pds_name="${bsky_host^} ($(atfile.util.get_region_pretty "$bsky_region"))" 599 pds_emoji="🍄" 600 ;; 601 "at.app.wafrn.net") pds_name="Wafrn"; pds_emoji="🌸" ;; 602 "atproto.brid.gy") pds_name="Bridgy Fed"; pds_emoji="🔀" ;; 603 "blacksky.app") pds_name="Blacksky"; pds_emoji="⬛" ;; 604 "pds.sprk.so") pds_name="Spark"; pds_emoji="✨" ;; 605 "tngl.sh") pds_name="Tangled"; pds_emoji="🪢" ;; 606 *) 607 pds_oauth_url="$pds/oauth/authorize" 608 pds_oauth_page="$(curl -H "User-Agent: $(atfile.util.get_uas)" -s -L -X GET "$pds_oauth_url" | tr -d '\n')" 609 pds_customization_data="$(echo "$pds_oauth_page" | sed -n 's/.*window\["__customizationData"\]=JSON.parse("\(.*\)");.*/\1/p' | sed 's/\\"/"/g; s/\\\\/\\/g' | sed 's/");window\[".*$//')" 610 611 if [[ $pds_customization_data == "{"* ]]; then 612 pds_name="$(echo "$pds_customization_data" | jq -r '.name')" 613 pds_emoji="🟦" 614 else 615 pds_name="$pds_host" 616 fi 617 ;; 618 esac 619 620 # BUG: Haiku Terminal has issues with emojis 621 if [[ -n "$pds_emoji" ]] && [[ $_os != "haiku" ]]; then 622 echo "$pds_emoji $pds_name" 623 else 624 echo "$pds_name" 625 fi 626} 627 628function atfile.util.get_random() { 629 amount="$1" 630 [[ -z "$amount" ]] && amount="6" 631 echo "$(tr -dc A-Za-z0-9 </dev/urandom | head -c "$amount"; echo)" 632} 633 634function atfile.util.get_random_pbc_jetstream() { 635 pbc_jetstreams=( 636 "jetstream1.us-east" 637 "jetstream2.us-east" 638 "jetstream1.us-west" 639 "jetstream2.us-east" 640 ) 641 642 pbc_jetstream="${pbc_jetstreams[ $RANDOM % ${#pbc_jetstreams[@]} ]}" 643 644 echo "wss://$pbc_jetstream.bsky.network" 645} 646 647function atfile.util.get_realpath() { 648 path="$1" 649 650 if [[ $_os == "solaris" ]]; then 651 # SEE: https://stackoverflow.com/a/11554895 652 # INVESTIGATE: Use this for every OS? 653 # shellcheck disable=SC2015 654 [ -d "$path" ] && ( 655 # shellcheck disable=SC1007 656 cd_path= \cd "$1" 657 /bin/pwd 658 ) || ( 659 # shellcheck disable=SC1007 660 cd_path= \cd "$(dirname "$1")" && 661 printf "%s/%s\n" "$(/bin/pwd)" "$(basename "$1")" 662 ) 663 else 664 realpath "$path" 665 fi 666} 667 668function atfile.util.get_region_pretty() { 669 region="$1" 670 671 region_sub="$(echo "$region" | cut -d "-" -f 2)" 672 region="$(echo "$region" | cut -d "-" -f 1)" 673 674 echo "${region^^} ${region_sub^}" 675} 676 677function atfile.util.get_rkey_from_at_uri() { 678 at_uri="$1" 679 echo "$at_uri" | cut -d "/" -f 5 680} 681 682function atfile.util.get_seconds_since_start() { 683 current="$(atfile.util.get_date "" "%s")" 684 # shellcheck disable=SC2154 685 echo "$(( current - _start ))" 686} 687 688function atfile.util.get_term_cols() { 689 unset rows 690 691 if [ -x "$(command -v tput)" ]; then 692 cols=$(tput cols) 693 fi 694 695 if [[ -n $cols ]]; then 696 echo "$cols" 697 else 698 echo 80 699 fi 700} 701 702function atfile.util.get_term_rows() { 703 unset rows 704 705 if [ -x "$(command -v tput)" ]; then 706 rows=$(tput lines) 707 fi 708 709 if [[ -n $rows ]]; then 710 echo "$rows" 711 else 712 echo 30 713 fi 714} 715 716function atfile.util.get_var_from_file() { 717 file="$1" 718 719 if [[ -f "$file" ]]; then 720 variable="$2" 721 found_line="$(grep "\b${variable}=" "$file")" 722 723 if [[ -n "$found_line" ]] && [[ ! "$found_line" == \#* ]]; then 724 output="${found_line#"${variable}"=}" 725 output="${output%\"}" 726 output="${output#\"}" 727 728 if [[ $output == *"\$("* && $output == *")"* ]]; then 729 eval "echo \"$output\"" 730 else 731 echo "$output" 732 fi 733 fi 734 fi 735} 736 737function atfile.util.get_uas() { 738 # shellcheck disable=SC2154 739 echo "ATFile/$_version" 740} 741 742function atfile.util.get_xrpc_error() { 743 exit_code="$1" 744 data="$2" 745 746 if [[ $exit_code != 0 || -z "$data" || "$data" == "null" || "$data" == "{}" || "$data" == *"\"error\":"* ]]; then 747 if [[ "$data" == "{"* && "$data" == *"\"error\":"* ]]; then 748 error="$(echo "$data" | jq -r ".error")" 749 message="$(echo "$data" | jq -r ".message")" 750 751 if [[ -z $message ]]; then 752 echo "$error" 753 else 754 echo "[$error] $message" 755 fi 756 else 757 echo "?" 758 fi 759 else 760 echo "" 761 fi 762} 763 764function atfile.util.get_yn() { 765 yn="$1" 766 767 # shellcheck disable=SC2154 768 if [[ $_output_json == 0 ]]; then 769 if [[ $yn == 0 ]]; then 770 echo "No" 771 else 772 echo "Yes" 773 fi 774 else 775 if [[ $yn == 0 ]]; then 776 echo "false" 777 else 778 echo "true" 779 fi 780 fi 781} 782 783function atfile.util.is_null_or_empty() { 784 if [[ -z "$1" ]] || [[ "$1" == null ]]; then 785 echo 1 786 else 787 echo 0 788 fi 789} 790 791function atfile.util.is_url_accessible_in_browser() { 792 url="$1" 793 # shellcheck disable=SC2154 794 atfile.util.is_url_okay "$url" "$_test_desktop_uas" 795} 796 797function atfile.util.is_url_okay() { 798 url="$1" 799 uas="$2" 800 801 [[ -z "$uas" ]] && uas="$(atfile.util.get_uas)" 802 803 code="$(curl -H "User-Agent: $uas" -s -o /dev/null -w "%{http_code}" "$url")" 804 805 if [[ "$code" == 2* || "$code" == 3* ]]; then 806 echo 1 807 else 808 echo 0 809 fi 810} 811 812function atfile.util.launch_uri() { 813 uri="$1" 814 815 if [[ -n $DISPLAY ]] && [ -x "$(command -v xdg-open)" ]; then 816 xdg-open "$uri" 817 else 818 case $_os in 819 "haiku") open "$uri" ;; 820 "macos") open "$uri" ;; 821 *) echo "$uri" 822 esac 823 fi 824} 825 826function atfile.util.parse_at_uri() { 827 at_uri="$1" 828 segment="$2" 829 unset parsed_at_uri 830 831 case $segment in 832 "actor"|"did"|"handle") echo "$at_uri" | cut -d "/" -f 3 ;; 833 "collection") echo "$at_uri" | cut -d "/" -f 4 ;; 834 "key"|"rkey") echo "$at_uri" | cut -d "/" -f 5 ;; 835 *) echo "$at_uri" | cut -d "/" -f "$segment" ;; 836 esac 837} 838 839function atfile.util.get_uri_segment() { 840 uri="$1" 841 segment="$2" 842 unset parsed_uri 843 844 case $segment in 845 "host") echo "$uri" | cut -d "/" -f 3 ;; 846 "protocol") echo "$uri" | cut -d ":" -f 1 ;; 847 *) echo "$uri" | cut -d "/" -f "$segment" ;; 848 esac 849} 850 851# HACK: This essentially breaks the entire session (it overrides $_username and 852# $_server). If sourcing, use atfile.util.override_actor_reset() to 853# reset 854function atfile.util.override_actor() { 855 actor="$1" 856 857 [[ -z "$_server_original" ]] && _server_original="$_server" 858 [[ -z "$_username_original" ]] && _username_original="$_username" 859 [[ -z "$_fmt_blob_url_original" ]] && _fmt_blob_url_original="$_fmt_blob_url" 860 861 resolved_did="$(atfile.util.resolve_identity "$actor")" 862 error="$(atfile.util.get_xrpc_error $? "$resolved_did")" 863 [[ -n "$error" ]] && atfile.die.xrpc_error "Unable to resolve '$actor'" "$resolved_did" 864 865 _username="$(echo "$resolved_did" | cut -d "|" -f 1)" 866 _server="$(echo "$resolved_did" | cut -d "|" -f 2)" 867 _fmt_blob_url="$_fmt_blob_url_default" 868 869 atfile.say.debug "Overridden identity\n↳ DID: $_username\n↳ PDS: $_server\n↳ Blob URL: $_fmt_blob_url" 870} 871 872# NOTE: This is to help during sourcing if atfile.uitl.override_actor() has 873# been called 874function atfile.util.override_actor_reset() { 875 [[ -n "$_server_original" ]] && _server="$_server_original"; unset _server_original 876 [[ -n "$_username_original" ]] && _username="$_username_original"; unset _username_original 877 [[ -n "$_fmt_blob_url_original" ]] && _fmt_blob_url="$_fmt_blob_url_original"; unset _fmt_blob_url_original 878} 879 880function atfile.util.parse_exiftool_date() { 881 in_date="$1" 882 tz="$2" 883 884 date="$(echo "$in_date" | cut -d " " -f 1 | sed -e "s|:|-|g")" 885 time="$(echo "$in_date" | cut -d " " -f 2)" 886 887 echo "$date $time $tz" 888} 889 890function atfile.util.parse_version() { 891 version="$1" 892 version="$(echo "$version" | cut -d "+" -f 1)" 893 v_major="$(printf "%04d\n" "$(echo "$version" | cut -d "." -f 1)")" 894 v_minor="$(printf "%04d\n" "$(echo "$version" | cut -d "." -f 2)")" 895 v_rev="$(printf "%04d\n" "$(echo "$version" | cut -d "." -f 3)")" 896 echo "${v_major}${v_minor}${v_rev}" | sed 's/^0*//' 897} 898 899function atfile.util.print_blob_url_output() { 900 blob_uri="$1" 901 902 run_cmd="$_prog url $key" 903 [[ -n "$_username_original" ]] && run_cmd+=" $_username" 904 905 if [[ $(atfile.util.is_url_accessible_in_browser "$blob_uri") == 0 ]]; then 906 echo -e "↳ Blob: ⚠️ Blob cannot be viewed in a browser\n Run '$run_cmd' to get URL" 907 else 908 echo -e "↳ Blob: $blob_uri" 909 fi 910} 911 912function atfile.util.print_override_envvar_debug() { 913 pretty_name="$1" 914 override_variable="$2" 915 916 override_value="$(eval echo "\$_force${override_variable}")" 917 envvar_suffix="_FORCE${override_variable^^}" 918 919 atfile.say.debug "Overriding $pretty_name (\$$override_variable)\n↳ ${_envvar_prefix}$envvar_suffix set to '$override_value'" 920} 921 922function atfile.util.print_seconds_since_start_debug() { 923 seconds=$(atfile.util.get_seconds_since_start) 924 second_unit="$(atfile.util.get_int_suffix "$seconds" "second" "seconds")" 925 926 atfile.say.debug "$seconds $second_unit since start" 927} 928 929function atfile.util.print_table_paginate_hint() { 930 cursor="$1" 931 count="$2" 932 933 # shellcheck disable=SC2154 934 if [[ -z $count ]] || (( ( count + _max_list_buffer ) >= _max_list )); then 935 first_line="List is limited to $_max_list results. To print more results," 936 first_line_length=$(( ${#first_line} + 3 )) 937 # shellcheck disable=SC2154 938 echo -e "$(atfile.util.repeat_char "-" $first_line_length)\nℹ️ $first_line\n run \`$_prog $_command $cursor\`" 939 fi 940} 941 942function atfile.util.repeat_char() { 943 char="$1" 944 amount="$2" 945 946 if [ -x "$(command -v seq)" ]; then 947 printf "%0.s$char" $(seq 1 "$amount") 948 else 949 echo "$char" 950 fi 951} 952 953function atfile.util.resolve_identity() { 954 actor="$1" 955 956 if [[ "$actor" != "did:"* ]]; then 957 # shellcheck disable=SC2154 958 resolved_handle="$(atfile.xrpc.bsky.get "com.atproto.identity.resolveHandle" "handle=$actor")" 959 error="$(atfile.util.get_xrpc_error $? "$resolved_handle")" 960 961 if [[ -z "$error" ]]; then 962 actor="$(echo "$resolved_handle" | jq -r ".did")" 963 fi 964 fi 965 966 if [[ "$actor" == "did:"* ]]; then 967 unset did_doc 968 969 case "$actor" in 970 "did:plc:"*) did_doc="$(atfile.util.get_didplc_doc "$actor")" ;; 971 "did:web:"*) did_doc="$(curl -H "User-Agent: $(atfile.util.get_uas)" -s -L -X GET "$(atfile.util.get_didweb_doc_url "$actor")")" ;; 972 *) echo "Unknown DID type 'did:$(echo "$actor" | cut -d ":" -f 2)'"; exit 255;; 973 esac 974 975 if [[ -n "$did_doc" ]]; then 976 did="$(echo "$did_doc" | jq -r ".id")" 977 978 if [[ $(atfile.util.is_null_or_empty "$did") == 1 ]]; then 979 echo "$error" 980 exit 255 981 fi 982 983 unset aliases 984 unset handle 985 didplc_dir="$(echo "$did_doc" | jq -r ".directory")" 986 pds="$(echo "$did_doc" | jq -r '.service[] | select(.id == "#atproto_pds") | .serviceEndpoint')" 987 988 while IFS=$'\n' read -r a; do 989 aliases+="$a;" 990 991 if [[ -z $handle && "$a" == "at://"* && "$a" != "at://did:"* ]]; then 992 handle="$a" 993 fi 994 done <<< "$(echo "$did_doc" | jq -r '.alsoKnownAs[]')" 995 996 [[ $didplc_dir == "null" ]] && unset didplc_dir 997 [[ -z "$handle" ]] && handle="invalid.handle" 998 999 echo "$did|$pds|$handle|$didplc_dir|$aliases" 1000 fi 1001 else 1002 echo "$error" 1003 exit 255 1004 fi 1005} 1006 1007function atfile.util.source_hook() { 1008 file="$1" 1009 1010 if [[ -n "$file" ]]; then 1011 atfile.say.debug "Sourcing: $file" 1012 if [[ -f "$file" ]]; then 1013 # shellcheck disable=SC1090 1014 . "$file" 1015 else 1016 atfile.die "Unable to source '$file'" 1017 fi 1018 fi 1019} 1020 1021function atfile.util.write_cache() { 1022 file="$1" 1023 file_path="$_path_cache/$1" 1024 content="$2" 1025 1026 atfile.util.get_cache "$file" 1027 1028 echo -e "$content" > "$file_path" 1029 # shellcheck disable=SC2320 1030 # shellcheck disable=SC2181 1031 [[ $? != 0 ]] && atfile.die "Unable to write to cache file ($file)" 1032}