patreon.mjs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import * as dotenv from "dotenv"
  2. import fs from "fs"
  3. dotenv.config({ path: ".env.local" })
  4. const fetchJson = async (url) => {
  5. const res = await fetch(url, {
  6. headers: { Authorization: `Bearer ${process.env.PATREON_ACCESS_TOKEN}` },
  7. })
  8. return await res.json()
  9. }
  10. let url = `https://www.patreon.com/api/oauth2/v2/campaigns/${process.env.PATREON_CAMPAIGN_ID}/members?include=user,currently_entitled_tiers&fields%5Bmember%5D=full_name,lifetime_support_cents,patron_status,pledge_relationship_start,note&fields%5Buser%5D=image_url&fields%5Btier%5D=title`
  11. const membersByTiers = {}
  12. const tierMap = {}
  13. while (true) {
  14. console.log("Fetching page...")
  15. const data = await fetchJson(url)
  16. const profilePictureMap = {}
  17. data.included.forEach((included) => {
  18. switch (included.type) {
  19. case "user":
  20. profilePictureMap[included.id] = included.attributes.image_url
  21. break
  22. case "tier":
  23. tierMap[included.id] = included.attributes.title
  24. break
  25. }
  26. })
  27. data.data.forEach((member) => {
  28. const userId = member.relationships.user.data.id
  29. if (member.attributes.patron_status !== "active_patron") {
  30. return
  31. }
  32. const currentlyEntitledTiers =
  33. member.relationships.currently_entitled_tiers.data
  34. if (currentlyEntitledTiers?.length < 1) {
  35. return
  36. }
  37. const tierId = currentlyEntitledTiers[0].id
  38. const tierName = tierMap[tierId]
  39. const members = membersByTiers[tierName] || []
  40. members.push({
  41. id: userId,
  42. picture: profilePictureMap[userId],
  43. name: member.attributes.full_name,
  44. joinedAt: member.attributes.pledge_relationship_start,
  45. lifetimeSupportCents: member.attributes.lifetime_support_cents,
  46. note: member.attributes.note,
  47. tier: tierName,
  48. })
  49. membersByTiers[tierName] = members
  50. })
  51. url = data?.links?.next
  52. if (!url) {
  53. break
  54. }
  55. }
  56. // Sponsors are sorted by lifetimeSupportCents desc
  57. // Highlighted sponsors are sorted by lifetimeSupportCents desc
  58. // Silver sponsors are sorted by joinedAt asc, grandfathered ones have nofollow: false
  59. const silver = membersByTiers["Silver sponsor"]
  60. .map((member) => ({
  61. url: member.note,
  62. name: member.name,
  63. logo: member.picture,
  64. nofollow: true,
  65. date: member.joinedAt,
  66. }))
  67. .concat(
  68. membersByTiers["Silver sponsor (grandfathered)"].map((member) => ({
  69. url: member.note,
  70. name: member.name,
  71. logo: member.picture,
  72. nofollow: false,
  73. date: member.joinedAt,
  74. }))
  75. )
  76. .sort((a, b) => new Date(a.date) - new Date(b.date))
  77. .map((member) => ({
  78. url: member.url,
  79. logo: member.logo,
  80. name: member.name,
  81. url: member.url,
  82. nofollow: member.nofollow,
  83. }))
  84. const generalHighlighted = membersByTiers["Highlighted sponsor"]
  85. .sort((a, b) => b.lifetimeSupportCents - a.lifetimeSupportCents)
  86. .map((member) => member.name)
  87. const general = membersByTiers["Sponsor"]
  88. .sort((a, b) => b.lifetimeSupportCents - a.lifetimeSupportCents)
  89. .map((member) => member.name)
  90. fs.writeFile(
  91. "./data/patreon.json",
  92. JSON.stringify(
  93. {
  94. silver,
  95. generalHighlighted,
  96. general,
  97. },
  98. null,
  99. " "
  100. ),
  101. (err) => {
  102. if (err) {
  103. console.error(err)
  104. return
  105. }
  106. console.log("File updated")
  107. }
  108. )