Added supporters to readme and a script to update it
This commit is contained in:
1 parent
b001d77efb
commit
3c8c84f156
3 files changed
+328
-8
No files matched your search
@@ -1,13 +1,27 @@
|
||||
# AI Toolkit by Ostris
|
||||
|
||||
## Support my work
|
||||
## Support My Work
|
||||
|
||||
<a href="https://www.patreon.com/ostris" target="_blank">
|
||||
<img alt="Patreon - ostris" src="https://ostris.com/wp-content/uploads/2025/01/support-me-on-patreon.png" width="256" height="auto">
|
||||
</a>
|
||||
If you enjoy my work, or use it for commercial purposes, please consider sponsoring me so I can continue to maintain it. Every bit helps!
|
||||
|
||||
[Become a sponsor on GitHub](https://github.com/orgs/ostris) or [support me on Patreon](https://www.patreon.com/ostris).
|
||||
|
||||
Thank you to all my current supporters!
|
||||
|
||||
_Last updated: 2025-03-02_
|
||||
|
||||
## GitHub Sponsors
|
||||
|
||||
<a href="https://github.com/replicate" title="Replicate"><img src="https://avatars.githubusercontent.com/u/60410876?v=4" width="50" height="50" alt="Replicate" style="border-radius:50%"></a>
|
||||
|
||||
## Patreon Supporters
|
||||
|
||||
<a href="None" title="Al H"><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/570742/4ceb33453a5a4745b430a216aba9280f/eyJ3IjoyMDB9/1.jpg?token-time=2145916800&token-hash=wUzsI5cO5Evp2ukIGdSgBbvKeYgv5LSOQMa6Br33Rrs%3D" width="50" height="50" alt="Al H" style="border-radius:50%"></a> <a href="None" title="clement Delangue"><img src="https://c8.patreon.com/3/200/33158543" width="50" height="50" alt="clement Delangue" style="border-radius:50%"></a> <a href="None" title="Cosmosis"><img src="https://c8.patreon.com/3/200/70218846" width="50" height="50" alt="Cosmosis" style="border-radius:50%"></a> <a href="None" title="David Garrido"><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/27288932/6c35d2d961ee4e14a7a368c990791315/eyJ3IjoyMDB9/1.jpeg?token-time=2145916800&token-hash=dpFFssZXZM_KZMKQhl3uDwwusdFw1c_v9x_ChJU7_zc%3D" width="50" height="50" alt="David Garrido" style="border-radius:50%"></a> <a href="None" title="Doron Adler"><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/82763/f99cc484361d4b9d94fe4f0814ada303/eyJ3IjoyMDB9/1.jpeg?token-time=2145916800&token-hash=BpwC020pR3TRZ4r0RSCiSIOh-jmatkrpy1h2XU4sGa4%3D" width="50" height="50" alt="Doron Adler" style="border-radius:50%"></a> <a href="None" title="Eli Slugworth"><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/54890369/45cea21d82974c78bf43956de7fb0e12/eyJ3IjoyMDB9/2.jpeg?token-time=2145916800&token-hash=IK6OT6UpusHgdaC4y8IhK5XxXiP5TuLy3vjvgL77Fho%3D" width="50" height="50" alt="Eli Slugworth" style="border-radius:50%"></a> <a href="None" title="EmmanuelMr18"><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/98811435/3a3632d1795b4c2b9f8f0270f2f6a650/eyJ3IjoyMDB9/1.jpeg?token-time=2145916800&token-hash=93w8RMxwXlcM4X74t03u6P5_SrKvlm1IpjnD2SzVpJk%3D" width="50" height="50" alt="EmmanuelMr18" style="border-radius:50%"></a> <a href="None" title="HestoySeghuro ."><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/30931983/54ab4e4ceab946e79a6418d205f9ed51/eyJ3IjoyMDB9/1.png?token-time=2145916800&token-hash=LBmsSsMQZhO6yRZ_YyRwTgE6a7BVWrGNsAVveLXHXR0%3D" width="50" height="50" alt="HestoySeghuro ." style="border-radius:50%"></a> <a href="None" title="Jack Blakely"><img src="https://c8.patreon.com/3/200/4105384" width="50" height="50" alt="Jack Blakely" style="border-radius:50%"></a> <a href="None" title="Jason"><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/150257013/0e9e333d30294eef9f4d6821166966d8/eyJ3IjoyMDB9/1.png?token-time=2145916800&token-hash=SlJKtKXncSL7uPJ8ES_ohz3om1KocFwXLpXFhlSLY0Q%3D" width="50" height="50" alt="Jason" style="border-radius:50%"></a> <a href="None" title="Jean-Tristan Marin"><img src="https://c8.patreon.com/3/200/27791680" width="50" height="50" alt="Jean-Tristan Marin" style="border-radius:50%"></a> <a href="None" title="Jodh Singh"><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/131773947/eda3405aa582437db4582fce908c8739/eyJ3IjoyMDB9/1.png?token-time=2145916800&token-hash=S4Bh0sMqTNmJlo3uRr7co5d_kxvBjITemDTfi_1KrCA%3D" width="50" height="50" alt="Jodh Singh" style="border-radius:50%"></a> <a href="None" title="John Dopamine"><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/103077711/bb215761cc004e80bd9cec7d4bcd636d/eyJ3IjoyMDB9/2.jpeg?token-time=2145916800&token-hash=zvtBie29rRTKTXvAA2KhOI-l3mSMk9xxr-mg_CksLtc%3D" width="50" height="50" alt="John Dopamine" style="border-radius:50%"></a> <a href="None" title="Joseph Rocca"><img src="https://c8.patreon.com/3/200/93304" width="50" height="50" alt="Joseph Rocca" style="border-radius:50%"></a> <a href="None" title="Kristjan Retter"><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/152118848/3b15a43d71714552b5ed1c9f84e66adf/eyJ3IjoyMDB9/1.png?token-time=2145916800&token-hash=IEKE18CBHVZ3k-08UD7Dkb7HbiFHb84W0FATdLMI0Dg%3D" width="50" height="50" alt="Kristjan Retter" style="border-radius:50%"></a> <a href="None" title="Maciej Popławski"><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/24458495/7d79412e3b994ae5afb3cb7804be85ee/eyJ3IjoyMDB9/1.jpeg?token-time=2145916800&token-hash=WJFgCkwp3_vMdDeCxK9k6CSsBnELp7UcmEGDYgOV07w%3D" width="50" height="50" alt="Maciej Popławski" style="border-radius:50%"></a> <a href="None" title="Michael Levine"><img src="https://c8.patreon.com/3/200/22809690" width="50" height="50" alt="Michael Levine" style="border-radius:50%"></a> <a href="None" title="Miguel Lara"><img src="https://c8.patreon.com/3/200/83319230" width="50" height="50" alt="Miguel Lara" style="border-radius:50%"></a> <a href="None" title="Misch Strotz"><img src="https://c10.patreonusercontent.com/4/patreon-media/p/user/8654302/b0f5ebedc62a47c4b56222693e1254e9/eyJ3IjoyMDB9/1.jpeg?token-time=2145916800&token-hash=lpeicIh1_S-3Ji3W27gyiRB7iXurp8Bx8HAzDHftOuo%3D" width="50" height="50" alt="Misch Strotz" style="border-radius:50%"></a> <a href="None" title="Mohamed Oumoumad"><img src="https://Line truncated
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
I work on open source full time, which means I 100% rely on donations to make a living. If you find this project helpful, or use it in for commercial purposes, please consider donating to support my work on [Patreon](https://www.patreon.com/ostris) or [Github Sponsors](https://github.com/sponsors/ostris).
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
env_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), ".env")
|
||||
load_dotenv(dotenv_path=env_path)
|
||||
|
||||
# API credentials
|
||||
PATREON_TOKEN = os.getenv("PATREON_ACCESS_TOKEN")
|
||||
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
|
||||
GITHUB_USERNAME = os.getenv("GITHUB_USERNAME")
|
||||
GITHUB_ORG = os.getenv("GITHUB_ORG") # Organization name (optional)
|
||||
|
||||
# Output file
|
||||
README_PATH = "SUPPORTERS.md"
|
||||
|
||||
def fetch_patreon_supporters():
|
||||
"""Fetch current Patreon supporters"""
|
||||
print("Fetching Patreon supporters...")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {PATREON_TOKEN}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
url = "https://www.patreon.com/api/oauth2/v2/campaigns"
|
||||
|
||||
try:
|
||||
# First get the campaign ID
|
||||
campaign_response = requests.get(url, headers=headers)
|
||||
campaign_response.raise_for_status()
|
||||
campaign_data = campaign_response.json()
|
||||
|
||||
if not campaign_data.get('data'):
|
||||
print("No campaigns found for this Patreon account")
|
||||
return []
|
||||
|
||||
campaign_id = campaign_data['data'][0]['id']
|
||||
|
||||
# Now get the supporters for this campaign
|
||||
members_url = f"https://www.patreon.com/api/oauth2/v2/campaigns/{campaign_id}/members"
|
||||
params = {
|
||||
"include": "user",
|
||||
"fields[member]": "full_name,is_follower,patron_status", # Removed profile_url
|
||||
"fields[user]": "image_url"
|
||||
}
|
||||
|
||||
supporters = []
|
||||
while members_url:
|
||||
members_response = requests.get(members_url, headers=headers, params=params)
|
||||
members_response.raise_for_status()
|
||||
members_data = members_response.json()
|
||||
|
||||
# Process the response to extract active patrons
|
||||
for member in members_data.get('data', []):
|
||||
attributes = member.get('attributes', {})
|
||||
|
||||
# Only include active patrons
|
||||
if attributes.get('patron_status') == 'active_patron':
|
||||
name = attributes.get('full_name', 'Anonymous Supporter')
|
||||
|
||||
# Get user data which contains the profile image
|
||||
user_id = member.get('relationships', {}).get('user', {}).get('data', {}).get('id')
|
||||
profile_image = None
|
||||
profile_url = None # Removed profile_url since it's not supported
|
||||
|
||||
if user_id:
|
||||
for included in members_data.get('included', []):
|
||||
if included.get('id') == user_id and included.get('type') == 'user':
|
||||
profile_image = included.get('attributes', {}).get('image_url')
|
||||
break
|
||||
|
||||
supporters.append({
|
||||
'name': name,
|
||||
'profile_image': profile_image,
|
||||
'profile_url': profile_url, # This will be None
|
||||
'platform': 'Patreon',
|
||||
'amount': 0 # Placeholder, as Patreon API doesn't provide this in the current response
|
||||
})
|
||||
|
||||
# Handle pagination
|
||||
members_url = members_data.get('links', {}).get('next')
|
||||
|
||||
print(f"Found {len(supporters)} active Patreon supporters")
|
||||
return supporters
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error fetching Patreon data: {e}")
|
||||
print(f"Response content: {e.response.content if hasattr(e, 'response') else 'No response content'}")
|
||||
return []
|
||||
|
||||
def fetch_github_sponsors():
|
||||
"""Fetch current GitHub sponsors for a user or organization"""
|
||||
print("Fetching GitHub sponsors...")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {GITHUB_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json"
|
||||
}
|
||||
|
||||
# Determine if we're fetching for a user or an organization
|
||||
entity_type = "organization" if GITHUB_ORG else "user"
|
||||
entity_name = GITHUB_ORG if GITHUB_ORG else GITHUB_USERNAME
|
||||
|
||||
if not entity_name:
|
||||
print("Error: Neither GITHUB_USERNAME nor GITHUB_ORG is set")
|
||||
return []
|
||||
|
||||
# Different GraphQL query structure based on entity type
|
||||
if entity_type == "user":
|
||||
query = """
|
||||
query {
|
||||
user(login: "%s") {
|
||||
sponsorshipsAsMaintainer(first: 100) {
|
||||
nodes {
|
||||
sponsorEntity {
|
||||
... on User {
|
||||
login
|
||||
name
|
||||
avatarUrl
|
||||
url
|
||||
}
|
||||
... on Organization {
|
||||
login
|
||||
name
|
||||
avatarUrl
|
||||
url
|
||||
}
|
||||
}
|
||||
tier {
|
||||
monthlyPriceInDollars
|
||||
}
|
||||
isOneTimePayment
|
||||
isActive
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""" % entity_name
|
||||
else: # organization
|
||||
query = """
|
||||
query {
|
||||
organization(login: "%s") {
|
||||
sponsorshipsAsMaintainer(first: 100) {
|
||||
nodes {
|
||||
sponsorEntity {
|
||||
... on User {
|
||||
login
|
||||
name
|
||||
avatarUrl
|
||||
url
|
||||
}
|
||||
... on Organization {
|
||||
login
|
||||
name
|
||||
avatarUrl
|
||||
url
|
||||
}
|
||||
}
|
||||
tier {
|
||||
monthlyPriceInDollars
|
||||
}
|
||||
isOneTimePayment
|
||||
isActive
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""" % entity_name
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
"https://api.github.com/graphql",
|
||||
headers=headers,
|
||||
json={"query": query}
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
# Process the response - the path to the data differs based on entity type
|
||||
if entity_type == "user":
|
||||
sponsors_data = data.get('data', {}).get('user', {}).get('sponsorshipsAsMaintainer', {}).get('nodes', [])
|
||||
else:
|
||||
sponsors_data = data.get('data', {}).get('organization', {}).get('sponsorshipsAsMaintainer', {}).get('nodes', [])
|
||||
|
||||
sponsors = []
|
||||
for sponsor in sponsors_data:
|
||||
# Only include active sponsors
|
||||
if sponsor.get('isActive'):
|
||||
entity = sponsor.get('sponsorEntity', {})
|
||||
name = entity.get('name') or entity.get('login', 'Anonymous Sponsor')
|
||||
profile_image = entity.get('avatarUrl')
|
||||
profile_url = entity.get('url')
|
||||
amount = sponsor.get('tier', {}).get('monthlyPriceInDollars', 0)
|
||||
|
||||
sponsors.append({
|
||||
'name': name,
|
||||
'profile_image': profile_image,
|
||||
'profile_url': profile_url,
|
||||
'platform': 'GitHub Sponsors',
|
||||
'amount': amount
|
||||
})
|
||||
|
||||
print(f"Found {len(sponsors)} active GitHub sponsors for {entity_type} '{entity_name}'")
|
||||
return sponsors
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error fetching GitHub sponsors data: {e}")
|
||||
return []
|
||||
|
||||
def generate_readme(supporters):
|
||||
"""Generate a README.md file with supporter information"""
|
||||
print(f"Generating {README_PATH}...")
|
||||
|
||||
# Sort supporters by amount (descending) and then by name
|
||||
supporters.sort(key=lambda x: (-x['amount'], x['name'].lower()))
|
||||
|
||||
# Determine the proper footer links based on what's configured
|
||||
github_entity = GITHUB_ORG if GITHUB_ORG else GITHUB_USERNAME
|
||||
github_entity_type = "orgs" if GITHUB_ORG else "sponsors"
|
||||
github_sponsor_url = f"https://github.com/{github_entity_type}/{github_entity}"
|
||||
|
||||
with open(README_PATH, "w", encoding="utf-8") as f:
|
||||
f.write("## Support My Work\n\n")
|
||||
f.write("If you enjoy my work, or use it for commercial purposes, please consider sponsoring me so I can continue to maintain it. Every bit helps! \n\n")
|
||||
# Create appropriate call-to-action based on what's configured
|
||||
cta_parts = []
|
||||
if github_entity:
|
||||
cta_parts.append(f"[Become a sponsor on GitHub]({github_sponsor_url})")
|
||||
if PATREON_TOKEN:
|
||||
cta_parts.append("[support me on Patreon](https://www.patreon.com/ostris)")
|
||||
|
||||
if cta_parts:
|
||||
if GITHUB_ORG:
|
||||
f.write(f"{' or '.join(cta_parts)}.\n\n")
|
||||
f.write("Thank you to all my current supporters!\n\n")
|
||||
|
||||
f.write(f"_Last updated: {datetime.now().strftime('%Y-%m-%d')}_\n\n")
|
||||
|
||||
# Write GitHub Sponsors section
|
||||
github_sponsors = [s for s in supporters if s['platform'] == 'GitHub Sponsors']
|
||||
if github_sponsors:
|
||||
f.write("### GitHub Sponsors\n\n")
|
||||
for sponsor in github_sponsors:
|
||||
if sponsor['profile_image']:
|
||||
f.write(f"<a href=\"{sponsor['profile_url']}\" title=\"{sponsor['name']}\"><img src=\"{sponsor['profile_image']}\" width=\"50\" height=\"50\" alt=\"{sponsor['name']}\" style=\"border-radius:50%\"></a> ")
|
||||
else:
|
||||
f.write(f"[{sponsor['name']}]({sponsor['profile_url']}) ")
|
||||
f.write("\n\n")
|
||||
|
||||
# Write Patreon section
|
||||
patreon_supporters = [s for s in supporters if s['platform'] == 'Patreon']
|
||||
if patreon_supporters:
|
||||
f.write("### Patreon Supporters\n\n")
|
||||
for supporter in patreon_supporters:
|
||||
if supporter['profile_image']:
|
||||
f.write(f"<a href=\"{supporter['profile_url']}\" title=\"{supporter['name']}\"><img src=\"{supporter['profile_image']}\" width=\"50\" height=\"50\" alt=\"{supporter['name']}\" style=\"border-radius:50%\"></a> ")
|
||||
else:
|
||||
f.write(f"[{supporter['name']}]({supporter['profile_url']}) ")
|
||||
f.write("\n\n")
|
||||
|
||||
f.write("\n---\n\n")
|
||||
|
||||
|
||||
print(f"Successfully generated {README_PATH} with {len(supporters)} supporters!")
|
||||
|
||||
def main():
|
||||
"""Main function"""
|
||||
print("Starting supporter data collection...")
|
||||
|
||||
# Check if required environment variables are set
|
||||
missing_vars = []
|
||||
if not GITHUB_TOKEN:
|
||||
missing_vars.append("GITHUB_TOKEN")
|
||||
|
||||
# Either username or org is required for GitHub
|
||||
if not GITHUB_USERNAME and not GITHUB_ORG:
|
||||
missing_vars.append("GITHUB_USERNAME or GITHUB_ORG")
|
||||
|
||||
# Patreon token is optional but warn if missing
|
||||
patreon_enabled = bool(PATREON_TOKEN)
|
||||
|
||||
if missing_vars:
|
||||
print(f"Error: Missing required environment variables: {', '.join(missing_vars)}")
|
||||
print("Please add them to your .env file")
|
||||
return
|
||||
|
||||
if not patreon_enabled:
|
||||
print("Warning: PATREON_ACCESS_TOKEN not set. Will only fetch GitHub sponsors.")
|
||||
|
||||
# Fetch data from both platforms
|
||||
patreon_supporters = fetch_patreon_supporters() if PATREON_TOKEN else []
|
||||
github_sponsors = fetch_github_sponsors()
|
||||
|
||||
# Combine supporters from both platforms
|
||||
all_supporters = patreon_supporters + github_sponsors
|
||||
|
||||
if not all_supporters:
|
||||
print("No supporters found on either platform")
|
||||
return
|
||||
|
||||
# Generate README
|
||||
generate_readme(all_supporters)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,3 +0,0 @@
|
||||
- only do ema on main device? shouldne be needed other than saving and sampling
|
||||
- check when to unwrap model and what it does
|
||||
- disable timer for non main local
|
||||
Reference in new issue
Block a user