Skip to content

Commit 5d764ed

Browse files
Merge pull request #261 from MonoGame/users/dellis1972/automatepatreons
Add flow to update the patreon data
2 parents 105938e + 6ab8bad commit 5d764ed

File tree

6 files changed

+676
-115
lines changed

6 files changed

+676
-115
lines changed

.github/workflows/main.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ jobs:
3636
GH_TOKEN: ${{ secrets.SPONSORS_TOKEN }}
3737
GH_LOGIN: MonoGame
3838

39+
- name: Fetch Patreons
40+
uses: ./FetchPatreons
41+
env:
42+
PATREON_ACCESS_TOKEN: ${{ secrets.PATREON_ACCESS_TOKEN }}
43+
3944
- name: Setup .NET Core SDK
4045
uses: actions/setup-dotnet@v4
4146
with:

.github/workflows/pullrequest.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ jobs:
3232
GH_TOKEN: ${{ secrets.SPONSORS_TOKEN }}
3333
GH_LOGIN: MonoGame
3434

35+
- name: Fetch Patreons
36+
uses: ./FetchPatreons
37+
env:
38+
PATREON_ACCESS_TOKEN: ${{ secrets.PATREON_ACCESS_TOKEN }}
39+
3540
- name: Setup .NET Core SDK
3641
uses: actions/setup-dotnet@v4
3742
with:

FetchPatreons/action.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: Fetch Patreons
2+
description: 'Updates the Patreon list for the Monogame website'
3+
author: 'MonoGame Foundation'
4+
runs:
5+
using: "composite"
6+
steps:
7+
- name: Fetch
8+
if: runner.os == 'Linux'
9+
run: |
10+
./FetchPatreons/fetch_patreon_members.sh
11+
shell: bash
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/bin/bash
2+
# filepath: fetch_patreon_members.sh
3+
4+
set -e
5+
6+
# Check for required environment variable
7+
if [ -z "$PATREON_ACCESS_TOKEN" ]; then
8+
echo "Error: PATREON_ACCESS_TOKEN environment variable is not set"
9+
echo "Usage: export PATREON_ACCESS_TOKEN='your_token_here' && ./fetch_patreon_members.sh"
10+
exit 1
11+
fi
12+
13+
# Get campaign ID first (you'll need to get your campaign ID)
14+
# If you know your campaign ID, you can hardcode it here
15+
CAMPAIGN_ID="${PATREON_CAMPAIGN_ID:-}"
16+
17+
if [ -z "$CAMPAIGN_ID" ]; then
18+
echo "Fetching campaign ID..."
19+
CAMPAIGN_RESPONSE=$(curl -s -X GET \
20+
"https://www.patreon.com/api/oauth2/v2/campaigns" \
21+
-H "Authorization: Bearer $PATREON_ACCESS_TOKEN" \
22+
-H "Content-Type: application/json")
23+
24+
CAMPAIGN_ID=$(echo "$CAMPAIGN_RESPONSE" | jq -r '.data[0].id')
25+
26+
if [ -z "$CAMPAIGN_ID" ] || [ "$CAMPAIGN_ID" = "null" ]; then
27+
echo "Error: Could not fetch campaign ID"
28+
echo "Response: $CAMPAIGN_RESPONSE"
29+
exit 1
30+
fi
31+
32+
echo "Campaign ID: $CAMPAIGN_ID"
33+
fi
34+
35+
# Fetch members with pagination support
36+
OUTPUT_FILE="website/_data/patreons.json"
37+
TEMP_FILE=$(mktemp)
38+
ALL_MEMBERS="[]"
39+
NEXT_CURSOR=""
40+
41+
echo "Fetching Patreon members..."
42+
43+
while true; do
44+
# Build the URL with cursor for pagination
45+
if [ -z "$NEXT_CURSOR" ]; then
46+
URL="https://www.patreon.com/api/oauth2/v2/campaigns/${CAMPAIGN_ID}/members?include=currently_entitled_tiers,user&fields%5Bmember%5D=full_name,patron_status&fields%5Btier%5D=title&fields%5Buser%5D=full_name,url"
47+
else
48+
URL="https://www.patreon.com/api/oauth2/v2/campaigns/${CAMPAIGN_ID}/members?include=currently_entitled_tiers,user&fields%5Bmember%5D=full_name,patron_status&fields%5Btier%5D=title&fields%5Buser%5D=full_name,url&page%5Bcursor%5D=${NEXT_CURSOR}"
49+
fi
50+
51+
RESPONSE=$(curl -s -X GET "$URL" \
52+
-H "Authorization: Bearer $PATREON_ACCESS_TOKEN" \
53+
-H "Content-Type: application/json")
54+
55+
# Check for errors
56+
if echo "$RESPONSE" | jq -e '.errors' > /dev/null 2>&1; then
57+
echo "Error from API:"
58+
echo "$RESPONSE" | jq '.errors'
59+
exit 1
60+
fi
61+
62+
# Process the response
63+
echo "$RESPONSE" > "$TEMP_FILE"
64+
65+
# Extract members from this page
66+
PAGE_MEMBERS=$(jq '[
67+
.data[] |
68+
. as $member |
69+
{
70+
member_id: .id,
71+
name: (.attributes.full_name // ""),
72+
tier_id: (.relationships.currently_entitled_tiers.data[0].id // ""),
73+
patron_status: .attributes.patron_status
74+
}
75+
]' "$TEMP_FILE")
76+
77+
# Extract included data (tiers and users)
78+
INCLUDED=$(jq '.included // []' "$TEMP_FILE")
79+
80+
# Simplified merge - include all patrons with active status, excluding free tier
81+
PAGE_RESULT=$(jq -n \
82+
--argjson members "$PAGE_MEMBERS" \
83+
--argjson included "$INCLUDED" \
84+
'$members | map(
85+
. as $m |
86+
($included[] | select(.type == "tier" and .id == $m.tier_id)) as $tier |
87+
{
88+
name: $m.name,
89+
tier: ($tier.attributes.title // "Unknown"),
90+
active: ($m.patron_status == "active_patron")
91+
}
92+
) | map(select(.tier != "Unknown" and .tier != "" and .tier != "Free")) | sort_by(.tier) | reverse')
93+
94+
# Merge with all members
95+
ALL_MEMBERS=$(jq -n \
96+
--argjson all "$ALL_MEMBERS" \
97+
--argjson page "$PAGE_RESULT" \
98+
'$all + $page')
99+
100+
echo "Fetched $(echo "$PAGE_RESULT" | jq 'length') members..."
101+
102+
# Check for next page
103+
NEXT_CURSOR=$(jq -r '.meta.pagination.cursors.next // ""' "$TEMP_FILE")
104+
105+
if [ -z "$NEXT_CURSOR" ] || [ "$NEXT_CURSOR" = "null" ]; then
106+
break
107+
fi
108+
done
109+
110+
# Merge with existing data to preserve custom fields and historical data
111+
if [ -f "$OUTPUT_FILE" ]; then
112+
echo "Merging with existing data to preserve custom fields and historical patrons..."
113+
EXISTING_DATA=$(cat "$OUTPUT_FILE")
114+
115+
# Merge strategy:
116+
# 1. Update existing members with new data from API (name, tier, active status)
117+
# 2. Keep old members that aren't in the new data (they've left but we keep them for history)
118+
# 3. Add new members from API
119+
# 4. Preserve custom fields like 'url' from existing data
120+
MERGED_DATA=$(jq -n \
121+
--argjson new "$ALL_MEMBERS" \
122+
--argjson old "$EXISTING_DATA" \
123+
'
124+
# Create lookup maps
125+
($new | map({(.name): .}) | add) as $newMap |
126+
($old | map({(.name): .}) | add) as $oldMap |
127+
128+
# Get all unique names from both old and new
129+
(($new | map(.name)) + ($old | map(.name)) | unique) as $allNames |
130+
131+
# For each name, merge appropriately
132+
$allNames | map(
133+
. as $name |
134+
if $newMap[$name] and $oldMap[$name] then
135+
# Member exists in both - merge, keeping custom fields from old but updating tier/active from new
136+
$oldMap[$name] + $newMap[$name]
137+
elif $newMap[$name] then
138+
# New member - use from API
139+
$newMap[$name]
140+
else
141+
# Old member not in new data - keep as is (they left but we preserve history)
142+
$oldMap[$name]
143+
end
144+
)
145+
')
146+
147+
echo "$MERGED_DATA" | jq '.' > "$OUTPUT_FILE"
148+
else
149+
# No existing file, just save new data
150+
echo "$ALL_MEMBERS" | jq '.' > "$OUTPUT_FILE"
151+
fi
152+
153+
echo "Successfully saved $(jq 'length' "$OUTPUT_FILE") patrons to $OUTPUT_FILE"
154+
155+
# Clean up
156+
rm -f "$TEMP_FILE"

0 commit comments

Comments
 (0)