Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mastodon
GitHub Repository: mastodon/joinmastodon
Path: blob/main/patreon.mjs
1006 views
1
import * as dotenv from "dotenv"
2
import fs from "fs"
3
4
dotenv.config({ path: ".env.local" })
5
6
const fetchJson = async (url) => {
7
const res = await fetch(url, {
8
headers: { Authorization: `Bearer ${process.env.PATREON_ACCESS_TOKEN}` },
9
})
10
return await res.json()
11
}
12
13
const sleep = (waitTime) =>
14
new Promise((resolve) => setTimeout(resolve, waitTime))
15
16
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`
17
18
const membersByTiers = {}
19
const tierMap = {}
20
21
while (true) {
22
console.log("Fetching page...")
23
24
const data = await fetchJson(url)
25
const profilePictureMap = {}
26
27
if (!data.included) {
28
console.log("Unexpected response:", data)
29
break
30
}
31
32
data.included.forEach((included) => {
33
switch (included.type) {
34
case "user":
35
profilePictureMap[included.id] = included.attributes.image_url
36
break
37
case "tier":
38
tierMap[included.id] = included.attributes.title
39
break
40
}
41
})
42
43
data.data.forEach((member) => {
44
const userId = member.relationships.user.data.id
45
46
if (member.attributes.patron_status !== "active_patron") {
47
return
48
}
49
50
const currentlyEntitledTiers =
51
member.relationships.currently_entitled_tiers.data
52
53
if (currentlyEntitledTiers?.length < 1) {
54
return
55
}
56
57
const tierId = currentlyEntitledTiers[0].id
58
const tierName = tierMap[tierId]
59
const members = membersByTiers[tierName] || []
60
61
members.push({
62
id: userId,
63
picture: profilePictureMap[userId],
64
name: member.attributes.full_name,
65
joinedAt: member.attributes.pledge_relationship_start,
66
lifetimeSupportCents: member.attributes.lifetime_support_cents,
67
note: member.attributes.note,
68
tier: tierName,
69
})
70
71
membersByTiers[tierName] = members
72
})
73
74
url = data?.links?.next
75
76
if (!url) {
77
break
78
}
79
80
await sleep(1000)
81
}
82
83
// Sponsors are sorted by lifetimeSupportCents desc
84
// Highlighted sponsors are sorted by lifetimeSupportCents desc
85
// Silver sponsors are sorted by joinedAt asc, grandfathered ones have nofollow: false
86
87
const silver = membersByTiers["Silver sponsor"]
88
.map((member) => ({
89
url: member.note,
90
name: member.name,
91
logo: member.picture,
92
nofollow: true,
93
date: member.joinedAt,
94
}))
95
.concat(
96
membersByTiers["Silver sponsor (grandfathered)"].map((member) => ({
97
url: member.note,
98
name: member.name,
99
logo: member.picture,
100
nofollow: false,
101
date: member.joinedAt,
102
}))
103
)
104
.sort((a, b) => new Date(a.date) - new Date(b.date))
105
.map((member) => ({
106
url: member.url,
107
logo: member.logo,
108
name: member.name,
109
url: member.url,
110
nofollow: member.nofollow,
111
}))
112
const generalHighlighted = membersByTiers["Highlighted sponsor"]
113
.sort((a, b) => b.lifetimeSupportCents - a.lifetimeSupportCents)
114
.map((member) => member.name)
115
const general = membersByTiers["Sponsor"]
116
.sort((a, b) => b.lifetimeSupportCents - a.lifetimeSupportCents)
117
.map((member) => member.name)
118
119
fs.writeFile(
120
"./data/patreon.json",
121
JSON.stringify(
122
{
123
silver,
124
generalHighlighted,
125
general,
126
},
127
null,
128
" "
129
),
130
(err) => {
131
if (err) {
132
console.error(err)
133
return
134
}
135
136
console.log("File updated")
137
}
138
)
139
140