diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 8c5247ee..cba3f690 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -36,6 +36,11 @@ jobs: GH_TOKEN: ${{ secrets.SPONSORS_TOKEN }} GH_LOGIN: MonoGame + - name: Fetch Patreons + uses: ./FetchPatreons + env: + PATREON_ACCESS_TOKEN: ${{ secrets.PATREON_ACCESS_TOKEN }} + - name: Setup .NET Core SDK uses: actions/setup-dotnet@v4 with: diff --git a/.github/workflows/pullrequest.yaml b/.github/workflows/pullrequest.yaml index 30d44809..872c4562 100644 --- a/.github/workflows/pullrequest.yaml +++ b/.github/workflows/pullrequest.yaml @@ -32,6 +32,11 @@ jobs: GH_TOKEN: ${{ secrets.SPONSORS_TOKEN }} GH_LOGIN: MonoGame + - name: Fetch Patreons + uses: ./FetchPatreons + env: + PATREON_ACCESS_TOKEN: ${{ secrets.PATREON_ACCESS_TOKEN }} + - name: Setup .NET Core SDK uses: actions/setup-dotnet@v4 with: diff --git a/FetchPatreons/action.yml b/FetchPatreons/action.yml new file mode 100644 index 00000000..98ddb22f --- /dev/null +++ b/FetchPatreons/action.yml @@ -0,0 +1,11 @@ +name: Fetch Patreons +description: 'Updates the Patreon list for the Monogame website' +author: 'MonoGame Foundation' +runs: + using: "composite" + steps: + - name: Fetch + if: runner.os == 'Linux' + run: | + ./FetchPatreons/fetch_patreon_members.sh + shell: bash \ No newline at end of file diff --git a/FetchPatreons/fetch_patreon_members.sh b/FetchPatreons/fetch_patreon_members.sh new file mode 100755 index 00000000..ebaf6ca5 --- /dev/null +++ b/FetchPatreons/fetch_patreon_members.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# filepath: fetch_patreon_members.sh + +set -e + +# Check for required environment variable +if [ -z "$PATREON_ACCESS_TOKEN" ]; then + echo "Error: PATREON_ACCESS_TOKEN environment variable is not set" + echo "Usage: export PATREON_ACCESS_TOKEN='your_token_here' && ./fetch_patreon_members.sh" + exit 1 +fi + +# Get campaign ID first (you'll need to get your campaign ID) +# If you know your campaign ID, you can hardcode it here +CAMPAIGN_ID="${PATREON_CAMPAIGN_ID:-}" + +if [ -z "$CAMPAIGN_ID" ]; then + echo "Fetching campaign ID..." + CAMPAIGN_RESPONSE=$(curl -s -X GET \ + "https://www.patreon.com/api/oauth2/v2/campaigns" \ + -H "Authorization: Bearer $PATREON_ACCESS_TOKEN" \ + -H "Content-Type: application/json") + + CAMPAIGN_ID=$(echo "$CAMPAIGN_RESPONSE" | jq -r '.data[0].id') + + if [ -z "$CAMPAIGN_ID" ] || [ "$CAMPAIGN_ID" = "null" ]; then + echo "Error: Could not fetch campaign ID" + echo "Response: $CAMPAIGN_RESPONSE" + exit 1 + fi + + echo "Campaign ID: $CAMPAIGN_ID" +fi + +# Fetch members with pagination support +OUTPUT_FILE="website/_data/patreons.json" +TEMP_FILE=$(mktemp) +ALL_MEMBERS="[]" +NEXT_CURSOR="" + +echo "Fetching Patreon members..." + +while true; do + # Build the URL with cursor for pagination + if [ -z "$NEXT_CURSOR" ]; then + 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" + else + 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}" + fi + + RESPONSE=$(curl -s -X GET "$URL" \ + -H "Authorization: Bearer $PATREON_ACCESS_TOKEN" \ + -H "Content-Type: application/json") + + # Check for errors + if echo "$RESPONSE" | jq -e '.errors' > /dev/null 2>&1; then + echo "Error from API:" + echo "$RESPONSE" | jq '.errors' + exit 1 + fi + + # Process the response + echo "$RESPONSE" > "$TEMP_FILE" + + # Extract members from this page + PAGE_MEMBERS=$(jq '[ + .data[] | + . as $member | + { + member_id: .id, + name: (.attributes.full_name // ""), + tier_id: (.relationships.currently_entitled_tiers.data[0].id // ""), + patron_status: .attributes.patron_status + } + ]' "$TEMP_FILE") + + # Extract included data (tiers and users) + INCLUDED=$(jq '.included // []' "$TEMP_FILE") + + # Simplified merge - include all patrons with active status, excluding free tier + PAGE_RESULT=$(jq -n \ + --argjson members "$PAGE_MEMBERS" \ + --argjson included "$INCLUDED" \ + '$members | map( + . as $m | + ($included[] | select(.type == "tier" and .id == $m.tier_id)) as $tier | + { + name: $m.name, + tier: ($tier.attributes.title // "Unknown"), + active: ($m.patron_status == "active_patron") + } + ) | map(select(.tier != "Unknown" and .tier != "" and .tier != "Free")) | sort_by(.tier) | reverse') + + # Merge with all members + ALL_MEMBERS=$(jq -n \ + --argjson all "$ALL_MEMBERS" \ + --argjson page "$PAGE_RESULT" \ + '$all + $page') + + echo "Fetched $(echo "$PAGE_RESULT" | jq 'length') members..." + + # Check for next page + NEXT_CURSOR=$(jq -r '.meta.pagination.cursors.next // ""' "$TEMP_FILE") + + if [ -z "$NEXT_CURSOR" ] || [ "$NEXT_CURSOR" = "null" ]; then + break + fi +done + +# Merge with existing data to preserve custom fields and historical data +if [ -f "$OUTPUT_FILE" ]; then + echo "Merging with existing data to preserve custom fields and historical patrons..." + EXISTING_DATA=$(cat "$OUTPUT_FILE") + + # Merge strategy: + # 1. Update existing members with new data from API (name, tier, active status) + # 2. Keep old members that aren't in the new data (they've left but we keep them for history) + # 3. Add new members from API + # 4. Preserve custom fields like 'url' from existing data + MERGED_DATA=$(jq -n \ + --argjson new "$ALL_MEMBERS" \ + --argjson old "$EXISTING_DATA" \ + ' + # Create lookup maps + ($new | map({(.name): .}) | add) as $newMap | + ($old | map({(.name): .}) | add) as $oldMap | + + # Get all unique names from both old and new + (($new | map(.name)) + ($old | map(.name)) | unique) as $allNames | + + # For each name, merge appropriately + $allNames | map( + . as $name | + if $newMap[$name] and $oldMap[$name] then + # Member exists in both - merge, keeping custom fields from old but updating tier/active from new + $oldMap[$name] + $newMap[$name] + elif $newMap[$name] then + # New member - use from API + $newMap[$name] + else + # Old member not in new data - keep as is (they left but we preserve history) + $oldMap[$name] + end + ) + ') + + echo "$MERGED_DATA" | jq '.' > "$OUTPUT_FILE" +else + # No existing file, just save new data + echo "$ALL_MEMBERS" | jq '.' > "$OUTPUT_FILE" +fi + +echo "Successfully saved $(jq 'length' "$OUTPUT_FILE") patrons to $OUTPUT_FILE" + +# Clean up +rm -f "$TEMP_FILE" \ No newline at end of file diff --git a/website/_data/patreons.json b/website/_data/patreons.json index 2bcea6e4..83e7586d 100644 --- a/website/_data/patreons.json +++ b/website/_data/patreons.json @@ -1,115 +1,460 @@ [ - { - "name": "James Silva", - "tier": "EnvironmentMapEffect", - "url": "https://ska-studios.com/" - }, - { - "name": "D. Ezra Sidran, PhD", - "tier": "DrawUserPrimitives" - }, - { - "name": "Pirota Pirozou", - "tier": "Cornflower Blue" - }, - { - "name": "macabrett", - "tier": "Cornflower Blue" - }, - { - "name": "Michal Škoula", - "tier": "Cornflower Blue", - "url": "https://skoula.cz/" - }, - { - "name": "Ben", - "tier": "SpriteBatch" - }, - { - "name": "ajian_nedo", - "tier": "SpriteBatch" - }, - { - "name": "Ellpeck Games", - "tier": "DrawUserPrimitives", - "url": "https://games.ellpeck.de/" - }, - { - "name": "Csharpskolan", - "tier": "Cornflower Blue" - }, - { - "name": "Donny Beals", - "tier": "SpriteBatch" - }, - { - "name": "Rui Holdorf", - "tier": "SpriteBatch" - }, - { - "name": "Alex Campbell-Black", - "tier": "Cornflower Blue" - }, - { - "name": "NotExplosive", - "tier": "SpriteBatch" - }, - { - "name": "Jake Kirkbride", - "tier": "DrawUserPrimitives" - }, - { - "name": "Oblotzky", - "tier": "DrawUserPrimitives" - }, - { - "name": "Gerald Unterrainer", - "tier": "SpriteBatch" - }, - { - "name":"Aaron Langille", - "tier": "SpriteBatch", - "url": "https://welcomebraveadventurer.ca" - }, - { - "name": "MoonFarmGame", - "tier": "SpriteBatch", - "url": "https://x.com/MoonFarmGame" - }, - { - "name": "MrValentine", - "tier": "SpriteBatch" - }, - { - "name": "aristurtledev", - "tier": "EnvironmentMapEffect", - "url": "https://aristurtledev.github.io/" - }, - { - "name": "Christopher Hanna", - "tier": "SpriteBatch" - }, - { - "name": "RenewEarth", - "tier": "SpriteBatch" - }, - { - "name": "Andrew Powell", - "tier": "SpriteBatch" - }, - { - "name": "Cynthia McMahon", - "tier": "SpriteBatch", - "url": "https://cynthiamcmahon.ca/blog/" - }, - { - "name": "Austin Sojka", - "tier": "Model.Draw", - "url": "https://romansixvigaming.com/" - }, - { - "name": "Dominik Gotojuch", - "tier": "DrawUserPrimitives", - "url": "https://gotojuch.com/" - } + { + "name": "Aaron Langille", + "tier": "SpriteBatch", + "url": "https://welcomebraveadventurer.ca", + "active": true + }, + { + "name": "Alex Campbell-Black", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Anastasia Lyra Calico", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Anders Pantzar", + "tier": "DrawUserPrimitives", + "active": true + }, + { + "name": "Andrew Powell", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Austin Sojka", + "tier": "Model.Draw", + "url": "https://romansixvigaming.com/", + "active": true + }, + { + "name": "Ben", + "tier": "SpriteBatch", + "active": false + }, + { + "name": "Ben Ritter", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Benjamin Sprakel", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Brandon Edgar", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Brett", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "BrizzleBrip", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Broken Stick", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Charles Pratt", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Christopher Hanna", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Christopher Schillinger", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Crizzhd ", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Csharpskolan", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Cynthia McMahon", + "tier": "SpriteBatch", + "url": "https://cynthiamcmahon.ca/blog/", + "active": true + }, + { + "name": "D. Ezra Sidran, PhD", + "tier": "DrawUserPrimitives", + "active": false + }, + { + "name": "Daddel Dattel Games", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Dave Pergola", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Den ", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Devin", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Dominik Gotojuch", + "tier": "DrawUserPrimitives", + "url": "https://gotojuch.com/", + "active": true + }, + { + "name": "Dominique Lacombe", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Donny Beals", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Eduardo Costa Rodrigues", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Efrain Vega Jr", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Ellpeck ", + "tier": "DrawUserPrimitives", + "active": true + }, + { + "name": "Ellpeck Games", + "tier": "DrawUserPrimitives", + "url": "https://games.ellpeck.de/", + "active": false + }, + { + "name": "Fabian Jakobsson", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Florian", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Gerald Unterrainer", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Hunter Krieger", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Jake Kirkbride", + "tier": "DrawUserPrimitives", + "active": true + }, + { + "name": "James Silva", + "tier": "EnvironmentMapEffect", + "url": "https://ska-studios.com/", + "active": true + }, + { + "name": "Joe Nicholson", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "John", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Joshua Favo", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Kelsam ", + "tier": "DrawUserPrimitives", + "active": true + }, + { + "name": "Landon", + "tier": "EnvironmentMapEffect", + "active": true + }, + { + "name": "Laurie Bose", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Lee Tran", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "MATTHEW PURCHESE", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Magnus Haaland", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Marcel Van Drunen", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Max Piskunov", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Maxime Caignart", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Melvin Bos", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Michal Škoula", + "tier": "Cornflower Blue", + "url": "https://skoula.cz/", + "active": false + }, + { + "name": "Mitchel Disveld", + "tier": "DrawUserPrimitives", + "active": true + }, + { + "name": "MoonFarmGame", + "tier": "SpriteBatch", + "url": "https://x.com/MoonFarmGame", + "active": false + }, + { + "name": "MrValentine", + "tier": "SpriteBatch", + "active": false + }, + { + "name": "NashFlow", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Nicola ", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Nicolò Montalesi", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Noam Chapnik", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "NotExplosive", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Oblotzky", + "tier": "DrawUserPrimitives", + "active": false + }, + { + "name": "Oculon Prime", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Paul Demers", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Pirota Pirozou", + "tier": "Cornflower Blue", + "active": false + }, + { + "name": "Primebie1", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Raleigh Showalter", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "RenewEarth", + "tier": "SpriteBatch" + }, + { + "name": "Retrodad0001", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Rui Holdorf", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "SagaSeeker", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Sanders Lauture", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Sandra Lénárdová", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Sharpe ", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Skye Maidstone", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Spacemyname", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Stephan Oberhoff", + "tier": "DrawUserPrimitives", + "active": true + }, + { + "name": "Takeshi Maruyama", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Tom Ogburn", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Tyler Lutz", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "V R", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "Zeram", + "tier": "SpriteBatch", + "active": true + }, + { + "name": "ajian_nedo", + "tier": "SpriteBatch", + "active": false + }, + { + "name": "aristurtledev", + "tier": "EnvironmentMapEffect", + "url": "https://aristurtledev.github.io/", + "active": false + }, + { + "name": "capsulecity ", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "chromatic_cobra", + "tier": "DrawUserPrimitives", + "active": true + }, + { + "name": "foobar1307", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "macabrett", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "madsdj", + "tier": "DrawUserPrimitives", + "active": true + }, + { + "name": "paloobi", + "tier": "Cornflower Blue", + "active": true + }, + { + "name": "Łukasz", + "tier": "DrawUserPrimitives", + "active": true + }, + { + "name": "これは 聡明 な文章だ", + "tier": "SpriteBatch", + "active": true + } ] diff --git a/website/content/donate.njk b/website/content/donate.njk index 7a2a442a..03a0aefe 100644 --- a/website/content/donate.njk +++ b/website/content/donate.njk @@ -63,7 +63,7 @@ title: Donate

Monthly Sponsors

- The following is a partial list of individuals and studios that support us through Patreon. + The following is a list of individuals and studios that support us through Patreon.


@@ -87,7 +87,46 @@ title: Donate {% for tier in tiers %}
{% for patreon in patreons %} - {% if patreon.tier == tier.name %} + {% if patreon.tier == tier.name and patreon.active %} + {% if patreon.url %}{% endif %} + {{patreon.name}} + {% if patreon.url %}{% endif %} +
+ {% endif %} + {% endfor %} +
+ {% endfor%} +
+ +
+
+

Past Monthly Sponsors

+

+ The following is a list of individuals and studios that have supported us through Patreon in the past. +

+
+
+ {% set tiers = [ + { name: "EnvironmentMapEffect", url: "https://www.patreon.com/checkout/MonoGame?rid=9142452" }, + { name: "Model.Draw", url: "https://www.patreon.com/checkout/MonoGame?rid=9142450" }, + { name: "DrawUserPrimitives", url: "https://www.patreon.com/checkout/MonoGame?rid=1916865" }, + { name: "SpriteBatch", url: "https://www.patreon.com/checkout/MonoGame?rid=1867475" }, + { name: "Cornflower Blue", url: "https://www.patreon.com/checkout/MonoGame?rid=1867471" } + ] %} +
+ {% for tier in tiers %} +
+ {{tier.name}} +
+
+
+ {% endfor %} +
+
+ {% for tier in tiers %} +
+ {% for patreon in patreons %} + {% if patreon.tier == tier.name and not patreon.active %} {% if patreon.url %}{% endif %} {{patreon.name}} {% if patreon.url %}{% endif %}