Merge pull request 'enhancement/csv-output-and-refactor' (#2) from enhancement/csv-output-and-refactor into master
Reviewed-on: https://git.nickhepler.cloud/nick/wallos-fetcher/pulls/2
This commit is contained in:
commit
888e6aeef8
39
README.md
39
README.md
@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
Wallos Fetcher is a command-line utility designed to retrieve and export subscription data from a self-hosted [Wallos](https://github.com/WallosApp/Wallos) instance—an open-source personal subscription tracker.
|
Wallos Fetcher is a command-line utility designed to retrieve and export subscription data from a self-hosted [Wallos](https://github.com/WallosApp/Wallos) instance—an open-source personal subscription tracker.
|
||||||
|
|
||||||
This script helps you keep track of all your recurring expenses and share that information in human-readable or machine-readable formats, making it especially useful for budgeting, personal finance planning, and digital legacy preparation for family members.
|
This script helps you keep track of all your recurring expenses and share that information in human-readable or machine-readable formats. It's especially useful for budgeting, personal finance planning, and digital legacy preparation for family members.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
- 📄 **Markdown output by default** – easy to read, easy to share
|
- 📄 **Markdown output by default** – clean, readable reports
|
||||||
|
- 📊 **CSV output** – spreadsheet-friendly for Excel, Numbers, etc.
|
||||||
- 💾 Export as **Markdown**, **CSV**, or **JSON**
|
- 💾 Export as **Markdown**, **CSV**, or **JSON**
|
||||||
- 🔍 Optionally include **full subscription details** (e.g., cycle, auto-renew, currency)
|
- 🔍 Optionally include **full subscription details** (e.g., cycle, auto-renew, currency)
|
||||||
- 🗃 Output to a custom file
|
- 🗃 Output to a custom file
|
||||||
|
- 🧼 Modular, readable Bash script using secure temp file handling
|
||||||
- 🧰 Lightweight, portable, and dependency-minimal (requires `curl` and `jq`)
|
- 🧰 Lightweight, portable, and dependency-minimal (requires `curl` and `jq`)
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -44,6 +46,8 @@ chmod +x wallos-fetch.sh
|
|||||||
./wallos-fetch.sh [options]
|
./wallos-fetch.sh [options]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> ℹ️ This script requires `curl` and `jq`. Run `./wallos-fetch.sh --help` to see all options.
|
||||||
|
|
||||||
### ✅ Example Commands
|
### ✅ Example Commands
|
||||||
|
|
||||||
| Purpose | Command |
|
| Purpose | Command |
|
||||||
@ -90,11 +94,34 @@ Structured export for use in spreadsheets or data processing tools.
|
|||||||
- Default fields: `id,name,price,next_payment,category_name,payment_method_name`
|
- Default fields: `id,name,price,next_payment,category_name,payment_method_name`
|
||||||
- `--full` option adds: `currency_id,cycle,auto_renew`, etc.
|
- `--full` option adds: `currency_id,cycle,auto_renew`, etc.
|
||||||
|
|
||||||
|
```csv
|
||||||
|
"id","name","price","next_payment","category_name","payment_method_name"
|
||||||
|
"1","Netflix","17.99","2025-04-21","Entertainment","Credit Card"
|
||||||
|
"2","Spotify","9.99","2025-05-01","Music","PayPal"
|
||||||
|
```
|
||||||
|
|
||||||
### 📦 JSON
|
### 📦 JSON
|
||||||
|
|
||||||
Raw response data from the Wallos API—useful for backups or integrations.
|
Raw response data from the Wallos API—useful for backups, custom reporting, or integrations.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"subscriptions": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Netflix",
|
||||||
|
"price": 17.99,
|
||||||
|
"currency_id": "USD",
|
||||||
|
"next_payment": "2025-04-21",
|
||||||
|
"category_name": "Entertainment",
|
||||||
|
"payment_method_name": "Credit Card"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔐 Authentication
|
## 🔐 Authentication
|
||||||
|
|
||||||
You can set your API key in one of two ways:
|
You can set your API key in one of two ways:
|
||||||
@ -125,6 +152,8 @@ API_KEY="your_api_key_here"
|
|||||||
|
|
||||||
> ⚠️ Be cautious: hardcoding secrets in scripts can pose a security risk if the file is shared or version-controlled.
|
> ⚠️ Be cautious: hardcoding secrets in scripts can pose a security risk if the file is shared or version-controlled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 📜 License
|
## 📜 License
|
||||||
|
|
||||||
This project is licensed under the [GNU General Public License v3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html).
|
This project is licensed under the [GNU General Public License v3.0 or later](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||||
@ -134,7 +163,7 @@ This project is licensed under the [GNU General Public License v3.0 or later](ht
|
|||||||
## 🤝 Contributions
|
## 🤝 Contributions
|
||||||
|
|
||||||
Feedback, bug reports, and pull requests are welcome!
|
Feedback, bug reports, and pull requests are welcome!
|
||||||
Submit issues or improvements via your Gitea repository.
|
Submit issues or improvements via the Gitea repository.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -142,4 +171,4 @@ Submit issues or improvements via your Gitea repository.
|
|||||||
|
|
||||||
Wallos is an open-source personal subscription manager. Learn more or contribute to the main project here:
|
Wallos is an open-source personal subscription manager. Learn more or contribute to the main project here:
|
||||||
|
|
||||||
🔗 https://github.com/WallosApp/Wallos
|
🔗 https://github.com/WallosApp/Wallos
|
||||||
|
|||||||
190
wallos-fetch.sh
190
wallos-fetch.sh
@ -1,161 +1,123 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
VERSION="2025.04"
|
VERSION="2025.05"
|
||||||
|
|
||||||
# Configuration
|
|
||||||
HOST="https://your-api-host.com"
|
HOST="https://your-api-host.com"
|
||||||
ENDPOINT="/api/subscriptions/get_subscriptions.php"
|
ENDPOINT="/api/subscriptions/get_subscriptions.php"
|
||||||
API_KEY="${API_KEY:-$WALLOS_API_KEY}"
|
API_KEY="${API_KEY:-$WALLOS_API_KEY}"
|
||||||
|
|
||||||
# Defaults
|
|
||||||
MODE="md"
|
MODE="md"
|
||||||
FULL=false
|
FULL=false
|
||||||
OUTPUT="" # Will set default per mode later
|
OUTPUT=""
|
||||||
|
TMP_FILE=$(mktemp)
|
||||||
|
|
||||||
|
# 🚨 Dependencies check
|
||||||
|
for dep in curl jq; do
|
||||||
|
command -v "$dep" >/dev/null 2>&1 || {
|
||||||
|
echo "❌ Missing required dependency: $dep"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
# Show help
|
|
||||||
show_help() {
|
show_help() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
ℹ️ Wallos Fetcher Script - v$VERSION
|
ℹ️ Wallos Fetcher Script - v$VERSION
|
||||||
|
|
||||||
Usage: ./wallos-fetch.sh [OPTIONS]
|
Usage: ./wallos-fetch.sh [OPTIONS]
|
||||||
|
|
||||||
Fetch and export subscription data from a Wallos instance.
|
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--json Output raw JSON
|
--json Output raw JSON
|
||||||
--md Output Markdown table
|
--md Output Markdown table
|
||||||
--full Include more fields in CSV/Markdown
|
--csv Output CSV table
|
||||||
|
--full Include more fields
|
||||||
--output <file> Custom output filename
|
--output <file> Custom output filename
|
||||||
--help Show this help message and exit
|
--help Show this help message
|
||||||
--version Show version and exit
|
--version Show version
|
||||||
|
|
||||||
Default output is Markdown.
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
# Argument parser
|
parse_args() {
|
||||||
while [[ "$#" -gt 0 ]]; do
|
while [[ "$#" -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--json) MODE="json" ;;
|
--json) MODE="json" ;;
|
||||||
--md) MODE="md" ;;
|
--md) MODE="md" ;;
|
||||||
--full) FULL=true ;;
|
--csv) MODE="csv" ;;
|
||||||
--output)
|
--full) FULL=true ;;
|
||||||
shift
|
--output) shift; OUTPUT="$1" ;;
|
||||||
OUTPUT="$1"
|
--help) show_help; exit 0 ;;
|
||||||
;;
|
--version) echo "🧾 wallos-fetch v$VERSION"; exit 0 ;;
|
||||||
--help) show_help; exit 0 ;;
|
*) echo "Unknown option: $1"; show_help; exit 1 ;;
|
||||||
--version) echo "❓ wallos-fetch v$VERSION"; exit 0 ;;
|
esac
|
||||||
*) echo "Unknown option: $1" && show_help && exit 1 ;;
|
shift
|
||||||
esac
|
done
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
# Set default filenames if none specified
|
# Set default output filename
|
||||||
if [[ -z "$OUTPUT" ]]; then
|
[[ -z "$OUTPUT" ]] && OUTPUT="subscriptions.${MODE}"
|
||||||
case "$MODE" in
|
}
|
||||||
json) OUTPUT="subscriptions.json" ;;
|
|
||||||
md) OUTPUT="subscriptions.md" ;;
|
|
||||||
csv) OUTPUT="subscriptions.csv" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Construct the API URL
|
fetch_data() {
|
||||||
URL="${HOST}${ENDPOINT}?api_key=${API_KEY}"
|
URL="${HOST}${ENDPOINT}?api_key=${API_KEY}"
|
||||||
|
RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" "$URL")
|
||||||
|
STATUS=$(echo "$RESPONSE" | sed -n 's/^HTTP_STATUS://p')
|
||||||
|
BODY=$(echo "$RESPONSE" | sed '/^HTTP_STATUS:/d')
|
||||||
|
|
||||||
# Perform the request
|
echo "$BODY" > "$TMP_FILE"
|
||||||
RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" "$URL")
|
|
||||||
BODY=$(echo "$RESPONSE" | sed -n '1,/^HTTP_STATUS:/p' | sed '$d')
|
|
||||||
STATUS=$(echo "$RESPONSE" | tr -d '\n' | sed -e 's/.*HTTP_STATUS://')
|
|
||||||
|
|
||||||
echo "HTTP Status: $STATUS"
|
if [[ "$STATUS" != "200" ]]; then
|
||||||
|
echo "❌ API request failed with status: $STATUS"
|
||||||
|
rm -f "$TMP_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
if [ "$STATUS" -eq 200 ]; then
|
output_json() {
|
||||||
echo "$BODY" > temp_response.json
|
mv "$TMP_FILE" "$OUTPUT"
|
||||||
|
echo "💾 Saved JSON to $OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
if [ "$MODE" == "json" ]; then
|
output_markdown() {
|
||||||
mv temp_response.json "$OUTPUT"
|
|
||||||
echo "💾 Saved JSON to $OUTPUT"
|
|
||||||
|
|
||||||
elif [ "$MODE" == "md" ]; then
|
|
||||||
# Write markdown header and description
|
|
||||||
{
|
{
|
||||||
echo "# 🧾 Active Subscriptions Overview"
|
echo "# 🧾 Active Subscriptions Overview"
|
||||||
echo ""
|
|
||||||
echo "This document lists all recurring subscriptions tied to this household or individual. It includes costs, renewal dates, categories, and payment methods. This record is provided as a reference for financial planning, digital legacy management, or to assist family members in case of emergencies or estate matters."
|
|
||||||
echo ""
|
|
||||||
echo "_Last updated: $(date +'%Y-%m-%d')_"
|
echo "_Last updated: $(date +'%Y-%m-%d')_"
|
||||||
echo ""
|
echo ""
|
||||||
} > "$OUTPUT"
|
} > "$OUTPUT"
|
||||||
|
|
||||||
if [ "$FULL" = true ]; then
|
if $FULL; then
|
||||||
echo "| ID | Name | Price | Currency | Next Payment | Cycle | Auto Renew | Category | Payment Method |" >> "$OUTPUT"
|
echo "| ID | Name | Price | Currency | Next Payment | Cycle | Auto Renew | Category | Payment Method |" >> "$OUTPUT"
|
||||||
echo "|----|------|-------|----------|---------------|-------|-------------|----------|----------------|" >> "$OUTPUT"
|
echo "|----|------|-------|----------|---------------|-------|-------------|----------|----------------|" >> "$OUTPUT"
|
||||||
jq -r '
|
jq -r '.subscriptions[] | [.id, .name, .price, .currency_id, .next_payment, .cycle, .auto_renew, .category_name, .payment_method_name] | @tsv' "$TMP_FILE" |
|
||||||
.subscriptions[] | [
|
while IFS=$'\t' read -r id name price curr next cycle renew cat pay; do
|
||||||
.id,
|
|
||||||
.name,
|
|
||||||
(.price | tostring),
|
|
||||||
.currency_id,
|
|
||||||
.next_payment,
|
|
||||||
.cycle,
|
|
||||||
.auto_renew,
|
|
||||||
(.category_name | gsub(">"; ">") | gsub("&"; "&")),
|
|
||||||
.payment_method_name
|
|
||||||
] | @tsv
|
|
||||||
' temp_response.json | while IFS=$'\t' read -r id name price curr next cycle renew cat pay; do
|
|
||||||
echo "| $id | $name | $price | $curr | $next | $cycle | $renew | $cat | $pay |" >> "$OUTPUT"
|
echo "| $id | $name | $price | $curr | $next | $cycle | $renew | $cat | $pay |" >> "$OUTPUT"
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
echo "| ID | Name | Price | Next Payment | Category | Payment Method |" >> "$OUTPUT"
|
echo "| ID | Name | Price | Next Payment | Category | Payment Method |" >> "$OUTPUT"
|
||||||
echo "|----|------|-------|---------------|----------|----------------|" >> "$OUTPUT"
|
echo "|----|------|-------|---------------|----------|----------------|" >> "$OUTPUT"
|
||||||
jq -r '
|
jq -r '.subscriptions[] | [.id, .name, .price, .next_payment, .category_name, .payment_method_name] | @tsv' "$TMP_FILE" |
|
||||||
.subscriptions[] | [
|
while IFS=$'\t' read -r id name price next cat pay; do
|
||||||
.id,
|
|
||||||
.name,
|
|
||||||
(.price | tostring),
|
|
||||||
.next_payment,
|
|
||||||
(.category_name | gsub(">"; ">") | gsub("&"; "&")),
|
|
||||||
.payment_method_name
|
|
||||||
] | @tsv
|
|
||||||
' temp_response.json | while IFS=$'\t' read -r id name price next cat pay; do
|
|
||||||
echo "| $id | $name | $price | $next | $cat | $pay |" >> "$OUTPUT"
|
echo "| $id | $name | $price | $next | $cat | $pay |" >> "$OUTPUT"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
rm -f temp_response.json
|
echo "💾 Saved Markdown to $OUTPUT"
|
||||||
echo "💾 Saved Markdown table to $OUTPUT"
|
rm -f "$TMP_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
output_csv() {
|
||||||
|
if $FULL; then
|
||||||
|
echo '"id","name","price","currency_id","next_payment","cycle","auto_renew","category_name","payment_method_name"' > "$OUTPUT"
|
||||||
|
jq -r '.subscriptions[] | [.id, .name, .price, .currency_id, .next_payment, .cycle, .auto_renew, .category_name, .payment_method_name] | @csv' "$TMP_FILE" >> "$OUTPUT"
|
||||||
else
|
else
|
||||||
if [ "$FULL" = true ]; then
|
echo '"id","name","price","next_payment","category_name","payment_method_name"' > "$OUTPUT"
|
||||||
echo '"id","name","price","currency_id","next_payment","cycle","auto_renew","category_name","payment_method_name"' > "$OUTPUT"
|
jq -r '.subscriptions[] | [.id, .name, .price, .next_payment, .category_name, .payment_method_name] | @csv' "$TMP_FILE" >> "$OUTPUT"
|
||||||
jq -r '
|
|
||||||
.subscriptions[] | [
|
|
||||||
.id,
|
|
||||||
.name,
|
|
||||||
.price,
|
|
||||||
.currency_id,
|
|
||||||
.next_payment,
|
|
||||||
.cycle,
|
|
||||||
.auto_renew,
|
|
||||||
.category_name,
|
|
||||||
.payment_method_name
|
|
||||||
] | @csv
|
|
||||||
' temp_response.json >> "$OUTPUT"
|
|
||||||
else
|
|
||||||
echo '"id","name","price","next_payment","category_name","payment_method_name"' > "$OUTPUT"
|
|
||||||
jq -r '
|
|
||||||
.subscriptions[] | [
|
|
||||||
.id,
|
|
||||||
.name,
|
|
||||||
.price,
|
|
||||||
.next_payment,
|
|
||||||
.category_name,
|
|
||||||
.payment_method_name
|
|
||||||
] | @csv
|
|
||||||
' temp_response.json >> "$OUTPUT"
|
|
||||||
fi
|
|
||||||
rm -f temp_response.json
|
|
||||||
echo "💾 Saved CSV to $OUTPUT"
|
|
||||||
fi
|
fi
|
||||||
else
|
echo "💾 Saved CSV to $OUTPUT"
|
||||||
echo "❌ Request failed. Response not saved."
|
rm -f "$TMP_FILE"
|
||||||
fi
|
}
|
||||||
|
|
||||||
|
# 🏁 Main
|
||||||
|
parse_args "$@"
|
||||||
|
fetch_data
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
json) output_json ;;
|
||||||
|
md) output_markdown ;;
|
||||||
|
csv) output_csv ;;
|
||||||
|
esac
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user