#!/usr/bin/env bash set -euo pipefail # See https://github.com/ImranR98/Obtainium/blob/main/lib/app_sources/rustore.dart # @describe Download apk from https://rustore.ru # @meta require-tools curl,jq,fzf,xxh64sum # @flag -u --update download file if it exists but hash not match # @flag -v --verbose verbose output # @flag --whats-new show whats new before download # @flag --save-json save package info JSON # @flag --extract extract ZIP on download # @option -q --query search string # @option -o --output=`_default_output` Output directory/file # @arg package_name Application package name # @env RU_GOV_CERTIFICATE_PATH! Path to Russian Ministry of Digital Development (MinCifry) certificates BASE_URL=https://backapi.rustore.ru PAGE_SIZE=10 _default_output() { pwd } _curl() { curl \ --cacert "$RU_GOV_CERTIFICATE_PATH" \ --retry 3 \ --header 'ruStoreVerCode: 1105002' \ "$@" } _aria2() { aria2c \ --ca-certificate="$RU_GOV_CERTIFICATE_PATH" \ --header='ruStoreVerCode: 1105002' \ "$@" } _download_file() { local output_file="$1" download_url="$2" if command -v aria2c >/dev/null; then local aria2_dir aria2_out aria2_dir=$(dirname "$output_file") aria2_out=$(basename "$output_file") _aria2 --dir="$aria2_dir" --out="$aria2_out" "$download_url" else _curl --progress-bar --output "$output_file" "$download_url" fi } _curl_silent() { _curl --silent "$@" } select_package_name_from_query() { local delimiter=$'\t' _curl_silent --get \ --data-urlencode "query=$argc_query" \ --data-urlencode "pageSize=$PAGE_SIZE" \ "$BASE_URL/applicationData/apps" | jq -r '.body.content[] | [.packageName,.appName,.shortDescription] | @tsv' | column -t -s "$delimiter" -o "$delimiter" | fzf --delimiter "$delimiter" --accept-nth 1 } get_app_info_by_package() { _curl_silent --get "$BASE_URL/applicationData/overallInfo/$1" | jq .body } download_apk() { local app_info="$1" local app_id local package_name local version_name app_id=$(jq -r .appId <<<"$app_info") package_name=$(jq -r .packageName <<<"$app_info") version_name=$(jq -r .versionName <<<"$app_info") local output_file local output_file_hash if [ -d "${argc_output?}" ]; then output_file="$argc_output/$package_name-$version_name.apk" else output_file="$argc_output" fi output_file_hash="${output_file}.xxh64sum" local existing_file_hash if [ -f "$output_file" ] && [ -f "$output_file_hash" ]; then if [ -z "${argc_update:-}" ]; then echo "File already exists: $output_file" exit 1 else existing_file_hash=$(cat "$output_file_hash") fi fi local download_info download_info=$(_curl_silent --request POST --json '{"appId":'"$app_id"'}' "$BASE_URL/applicationData/v2/download-link" | jq .body.downloadUrls[0]) local download_url local expected_hash download_url=$(jq -r .url <<<"$download_info") expected_hash=$(jq -r .hash <<<"$download_info") [ "${existing_file_hash:-}" == "$expected_hash" ] && return [ -n "${argc_whats_new:-}" ] && jq -r '"News: \(.whatsNew)"' <<<"$app_info" temp_file_zip=$(mktemp) temp_file_apk=$(mktemp) trap 'rm -f "$temp_file_zip" "$temp_file_apk"' EXIT _download_file "$temp_file_zip" "$download_url" existing_file_hash=$(xxh64sum "$temp_file_zip" | cut -d ' ' -f 1) if [ "$existing_file_hash" == "$expected_hash" ]; then unzip -p "$temp_file_zip" "*.apk" >"$temp_file_apk" mv "$temp_file_apk" "$output_file" echo "$expected_hash" >"$output_file_hash" else echo "Downloaded file hash not match expected" exit 2 fi } main() { local package_name="${argc_package_name:-}" if [[ "$package_name" == http* ]]; then package_name=$(echo "$package_name" | grep -oP '(?<=/app/)[^/?]+') fi if [ -z "$package_name" ] && [ -n "${argc_query:-}" ]; then package_name=$(select_package_name_from_query) fi [ -z "$package_name" ] && exit 1 local app_info app_info=$(get_app_info_by_package "$package_name") local app_id app_id=$(jq -r .appId <<<"$app_info") if [ -z "${app_id:-}" ]; then echo "No application ID specified or found" exit 1 fi if [ "${argc_verbose:-}" == 1 ]; then jq -r '{appName,versionName,appVerUpdatedAt,shortDescription,whatsNew} | to_entries[] | [.key,.value] | @tsv' <<<"$app_info" | column -t -s $'\t' else jq -r '"\(.appName) - v\(.versionName) [\(.appVerUpdatedAt | strptime("%Y-%m-%dT%H:%M:%S%Z") | strftime("%d.%m.%Y"))]"' <<<"$app_info" fi download_apk "$app_info" } eval "$(argc --argc-eval "$0" "$@")"