fetch_sponsors.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. #!/usr/bin/env python3
  2. import json
  3. import os
  4. import re
  5. from pathlib import Path
  6. import httpx
  7. # NOTE:
  8. # requires a GitHub token with the necessary permissions read:org and read:user
  9. # call with `GITHUB_TOKEN=ghp_XXX ./fetch_sponsors.py`
  10. response = httpx.post(
  11. 'https://api.github.com/graphql',
  12. json={
  13. 'query': '''
  14. query($organization: String!) {
  15. organization(login: $organization) {
  16. sponsorshipsAsMaintainer(first: 100, includePrivate: true) {
  17. nodes {
  18. sponsorEntity {
  19. ... on User {
  20. login
  21. name
  22. url
  23. avatarUrl
  24. }
  25. ... on Organization {
  26. login
  27. name
  28. url
  29. avatarUrl
  30. }
  31. }
  32. tier {
  33. monthlyPriceInDollars
  34. name
  35. isOneTime
  36. }
  37. createdAt
  38. }
  39. }
  40. }
  41. }
  42. ''',
  43. 'variables': {'organization': 'zauberzeug'},
  44. },
  45. headers={
  46. 'Authorization': f'token {os.getenv("GITHUB_TOKEN")}',
  47. 'Accept': 'application/vnd.github.v3+json',
  48. },
  49. timeout=10.0,
  50. )
  51. response.raise_for_status()
  52. data = response.json()
  53. if 'errors' in data:
  54. raise RuntimeError(f'GitHub API Error: {data["errors"]}')
  55. sponsors = []
  56. for sponsor in data['data']['organization']['sponsorshipsAsMaintainer']['nodes']:
  57. sponsor_entity = sponsor['sponsorEntity']
  58. tier = sponsor['tier']
  59. sponsors.append({
  60. 'login': sponsor_entity['login'],
  61. 'name': sponsor_entity['name'],
  62. 'url': sponsor_entity['url'],
  63. 'avatar_url': sponsor_entity['avatarUrl'],
  64. 'tier_name': tier['name'],
  65. 'tier_amount': tier['monthlyPriceInDollars'],
  66. 'tier_is_one_time': tier['isOneTime'],
  67. 'created_at': sponsor['createdAt'],
  68. })
  69. sponsors.sort(key=lambda s: s['created_at'])
  70. contributors = []
  71. page = 1
  72. while True:
  73. contributors_response = httpx.get(
  74. f'https://api.github.com/repos/zauberzeug/nicegui/contributors?page={page}&per_page=100',
  75. headers={
  76. 'Authorization': f'token {os.getenv("GITHUB_TOKEN")}',
  77. 'Accept': 'application/vnd.github.v3+json',
  78. },
  79. timeout=10.0,
  80. )
  81. contributors_response.raise_for_status()
  82. page_contributors = contributors_response.json()
  83. if not page_contributors:
  84. break
  85. contributors.extend(page_contributors)
  86. page += 1
  87. print(f'Found {len(sponsors)} sponsors')
  88. print(f'Total contributors for NiceGUI: {len(contributors)}')
  89. Path('website/sponsors.json').write_text(json.dumps({
  90. 'top': [s['login'] for s in sponsors if s['tier_amount'] >= 100 and not s['tier_is_one_time']],
  91. 'total': len(sponsors),
  92. 'contributors': len(contributors),
  93. }, indent=2) + '\n', encoding='utf-8')
  94. sponsor_html = '<p align="center">\n'
  95. for sponsor in sponsors:
  96. if sponsor['tier_amount'] >= 25 and not sponsor['tier_is_one_time']:
  97. sponsor_html += f' <a href="{sponsor["url"]}"><img src="{sponsor["url"]}.png" width="50px" alt="{sponsor["name"]}" /></a>\n'
  98. sponsor_html += '</p>'
  99. readme_path = Path('README.md')
  100. readme_content = readme_path.read_text(encoding='utf-8')
  101. updated_content = re.sub(
  102. r'<!-- SPONSORS -->.*?<!-- SPONSORS -->',
  103. f'<!-- SPONSORS -->\n{sponsor_html}\n<!-- SPONSORS -->',
  104. readme_content,
  105. flags=re.DOTALL,
  106. )
  107. readme_path.write_text(updated_content, encoding='utf-8')
  108. print('README.md and sponsors.json updated successfully.')