@anitrack/patreon-wrapper 1.5.3 → 1.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,265 +1,309 @@
1
- import axios from 'axios';
2
-
3
- export type PatronStatus =
4
- | 'active_patron'
5
- | 'declined_patron'
6
- | 'former_patron';
7
-
8
- export type PatronAPIAuth = {
9
- AccessToken: string;
10
- CampaignID: string;
11
- };
12
-
13
- export type PatronType = {
14
- displayId: string;
15
- displayName: string;
16
- emailAddress: string;
17
- isFollower: boolean;
18
- subscription: {
19
- note: string;
20
- currentEntitled: {
21
- status: PatronStatus;
22
- tier: {
23
- id: string;
24
- title: string;
25
- };
26
- cents: number;
27
- willPayCents: number;
28
- lifetimeCents: number;
29
- firstCharge: string;
30
- nextCharge: string;
31
- lastCharge: string;
32
- };
33
- };
34
- mediaConnection: {
35
- patreon: {
36
- id: string;
37
- url: string;
38
- };
39
- discord: {
40
- id: string | null;
41
- url: string | null;
42
- };
43
- };
44
- };
45
-
46
- export type SandboxOptions = {
47
- displayId: string;
48
- displayName: string;
49
- emailAddress: string;
50
- tier: {
51
- id: string;
52
- title: string;
53
- };
54
- cents: number;
55
- willPayCents: number;
56
- lifetimeCents: number;
57
- patronStatus: PatronStatus;
58
- firstCharge: string;
59
- nextCharge: string;
60
- lastCharge: string;
61
- mediaConnection: {
62
- patreon: {
63
- id: string;
64
- url: string;
65
- };
66
- discord: {
67
- id: string | null;
68
- url: string | null;
69
- };
70
- };
71
- };
72
-
73
- export class Patreon {
74
- private static _URL: string = 'https://www.patreon.com/api/oauth2/v2/';
75
-
76
- private static _AccessToken: string;
77
- private static _CampaignID: string;
78
- private static _SandboxPatrons: Array<SandboxOptions> = [];
79
-
80
- public static Authorization(AuthCredentials: PatronAPIAuth) {
81
- if (!AuthCredentials.AccessToken || !AuthCredentials.CampaignID) {
82
- throw new Error(
83
- 'AccessToken and CampaignID are required on Authorization'
84
- );
85
- }
86
-
87
- this._AccessToken = AuthCredentials.AccessToken;
88
- this._CampaignID = AuthCredentials.CampaignID;
89
- }
90
-
91
- private static async FetchAPI(URI: string) {
92
- if (!this._AccessToken || !this._CampaignID) {
93
- throw new Error(
94
- 'AccessToken and CampaignID are required on Authorization'
95
- );
96
- }
97
-
98
- return await axios(this._URL + URI, {
99
- method: 'GET',
100
- headers: { Authorization: 'Bearer ' + this._AccessToken },
101
- }).catch((err: Error) => {
102
- throw new Error('Fetch API Failed...' + err);
103
- });
104
- }
105
-
106
- private static CleanURL(query: string) {
107
- query = query.replaceAll('[', '%5B').replaceAll(']', '%5D');
108
- query = query.replaceAll(' ', '');
109
-
110
- return query;
111
- }
112
-
113
- public static async FetchPatrons(
114
- filters: Array<PatronStatus> = ['active_patron'],
115
- pageSize: number = 450,
116
- showSandboxPatrons: boolean = false
117
- ) {
118
- const { data } = await this.FetchAPI(
119
- this.CleanURL(
120
- `campaigns/${this._CampaignID}/` +
121
- `members ? include = user, currently_entitled_tiers & page[count] = ${pageSize} & fields[member] = campaign_lifetime_support_cents, currently_entitled_amount_cents, email, full_name, is_follower, last_charge_date, last_charge_status, lifetime_support_cents, next_charge_date, note, patron_status, pledge_cadence, pledge_relationship_start, will_pay_amount_cents & fields[user] = social_connections & fields[tier] = title`
122
- )
123
- );
124
-
125
- const Patrons: Array<PatronType> = [];
126
- const PatreonAPIPatrons = data?.data || [];
127
-
128
- if (PatreonAPIPatrons.length == 0) return [];
129
-
130
- // Format Real Patrons
131
- for (let x = 0; x < PatreonAPIPatrons.length; x++) {
132
- const Relationships = PatreonAPIPatrons[x].relationships;
133
- const Attributes = PatreonAPIPatrons[x].attributes;
134
-
135
- if (!filters.includes(Attributes.patron_status)) continue;
136
-
137
- const socialInfo = data.included.find(
138
- (patron: any) =>
139
- patron.id == Relationships.user.data.id &&
140
- patron.type === 'user'
141
- );
142
-
143
- const tierInfo = data.included.find(
144
- (patron: any) =>
145
- patron.id ==
146
- Relationships.currently_entitled_tiers?.data[0]?.id &&
147
- patron.type === 'tier'
148
- );
149
-
150
- Patrons.push({
151
- displayId: Relationships.user.data.id,
152
- displayName: Attributes.full_name,
153
- emailAddress: Attributes.email,
154
- isFollower: Attributes.is_follower,
155
- subscription: {
156
- note: Attributes.note,
157
- currentEntitled: {
158
- status: Attributes.patron_status,
159
- tier: {
160
- id: tierInfo ? tierInfo.id : null,
161
- title: tierInfo ? tierInfo.attributes.title : null,
162
- },
163
- cents:
164
- Attributes.currently_entitled_amount_cents != 0
165
- ? Attributes.currently_entitled_amount_cents
166
- : null,
167
- willPayCents: Attributes.will_pay_amount_cents,
168
- lifetimeCents: Attributes.lifetime_support_cents,
169
- firstCharge: Attributes.pledge_relationship_start,
170
- nextCharge: Attributes.next_charge_date,
171
- lastCharge: Attributes.last_charge_date,
172
- },
173
- },
174
- mediaConnection: {
175
- patreon: {
176
- id: Relationships.user.data.id,
177
- url: Relationships.user.links.related,
178
- },
179
- discord: {
180
- id: socialInfo?.attributes?.social_connections?.discord
181
- ?.user_id
182
- ? socialInfo?.attributes?.social_connections
183
- ?.discord?.user_id
184
- : null,
185
- url:
186
- 'https://discordapp.com/users/' +
187
- socialInfo?.attributes?.social_connections?.discord
188
- ?.user_id
189
- ? socialInfo?.attributes?.social_connections
190
- ?.discord?.user_id
191
- : null,
192
- },
193
- },
194
- });
195
- }
196
-
197
- if (showSandboxPatrons) {
198
- // Format Sandbox Patrons
199
- for (let x = 0; x < this._SandboxPatrons.length; x++) {
200
- const Patron = this._SandboxPatrons[x];
201
-
202
- Patrons.push({
203
- displayId: Patron.displayId,
204
- displayName: Patron.displayName,
205
- emailAddress: Patron.emailAddress,
206
- isFollower: false,
207
- subscription: {
208
- note: 'Sandbox',
209
- currentEntitled: {
210
- status: Patron.patronStatus,
211
- tier: {
212
- id: Patron.tier.id,
213
- title: Patron.tier.title,
214
- },
215
- cents: Patron.cents,
216
- willPayCents: Patron.willPayCents,
217
- lifetimeCents: Patron.lifetimeCents,
218
- firstCharge: Patron.firstCharge,
219
- nextCharge: Patron.nextCharge,
220
- lastCharge: Patron.lastCharge,
221
- },
222
- },
223
- mediaConnection: {
224
- patreon: {
225
- id: Patron.mediaConnection.patreon.id,
226
- url: Patron.mediaConnection.patreon.url,
227
- },
228
- discord: {
229
- id: Patron?.mediaConnection?.discord?.id
230
- ? Patron?.mediaConnection?.discord?.id
231
- : null,
232
- url: Patron?.mediaConnection?.discord?.url
233
- ? Patron?.mediaConnection?.discord?.id
234
- : null,
235
- },
236
- },
237
- });
238
- }
239
- }
240
-
241
- return Patrons;
242
- }
243
-
244
- protected static _SandboxAddPatron(Patron: SandboxOptions) {
245
- this._SandboxPatrons.push(Patron);
246
- }
247
-
248
- protected static _SandboxGetPatron() {
249
- return this._SandboxPatrons;
250
- }
251
-
252
- // public static FetchPatron() {}
253
-
254
- // public static FetchCampaign() {}
255
- }
256
-
257
- export class Sandbox extends Patreon {
258
- public static GetPatrons() {
259
- return super._SandboxGetPatron();
260
- }
261
-
262
- public static AddPatron(Patron: SandboxOptions) {
263
- super._SandboxAddPatron(Patron);
264
- }
265
- }
1
+ import axios from 'axios';
2
+
3
+ export type PatronStatus =
4
+ | 'active_patron'
5
+ | 'declined_patron'
6
+ | 'former_patron';
7
+
8
+ export type PatronAPIAuth = {
9
+ AccessToken: string;
10
+ CampaignID: string;
11
+ };
12
+
13
+ export type PatronType = {
14
+ displayId: string;
15
+ displayName: string | null; // [2026-05-26] Identity Masking
16
+ emailAddress: string | null; // [2026-05-26] Identity Masking
17
+ isFollower: boolean;
18
+ subscription: {
19
+ note: string;
20
+ currentEntitled: {
21
+ status: PatronStatus;
22
+ lastChargeStatus: string | null;
23
+ tier: {
24
+ id: string;
25
+ title: string;
26
+ };
27
+ cents: number;
28
+ willPayCents: number;
29
+ lifetimeCents: number;
30
+ firstCharge: string;
31
+ nextCharge: string;
32
+ lastCharge: string;
33
+ };
34
+ };
35
+ mediaConnection: {
36
+ patreon: {
37
+ id: string;
38
+ url: string;
39
+ };
40
+ discord: {
41
+ id: string | null;
42
+ url: string | null;
43
+ };
44
+ };
45
+ };
46
+
47
+ export type CampaignType = {
48
+ id: string;
49
+ name: string;
50
+ patronCount: number;
51
+ currency: string;
52
+ isMonthly: boolean;
53
+ isNsfw: boolean;
54
+ summary: string | null;
55
+ createdAt: string;
56
+ publishedAt: string | null;
57
+ imageUrl: string | null;
58
+ imageSmallUrl: string | null;
59
+ discordServerId: string | null;
60
+ };
61
+
62
+ export type SandboxOptions = {
63
+ displayId: string;
64
+ displayName: string;
65
+ emailAddress: string;
66
+ tier: {
67
+ id: string;
68
+ title: string;
69
+ };
70
+ cents: number;
71
+ willPayCents: number;
72
+ lifetimeCents: number;
73
+ patronStatus: PatronStatus;
74
+ firstCharge: string;
75
+ nextCharge: string;
76
+ lastCharge: string;
77
+ mediaConnection: {
78
+ patreon: {
79
+ id: string;
80
+ url: string;
81
+ };
82
+ discord: {
83
+ id: string | null;
84
+ url: string | null;
85
+ };
86
+ };
87
+ };
88
+
89
+ export class Patreon {
90
+ private static _URL: string = 'https://www.patreon.com/api/oauth2/v2/';
91
+
92
+ private static _AccessToken: string;
93
+ private static _CampaignID: string;
94
+ private static _SandboxPatrons: Array<SandboxOptions> = [];
95
+
96
+ public static Authorization(AuthCredentials: PatronAPIAuth) {
97
+ if (!AuthCredentials.AccessToken || !AuthCredentials.CampaignID) {
98
+ throw new Error(
99
+ 'AccessToken and CampaignID are required on Authorization'
100
+ );
101
+ }
102
+
103
+ this._AccessToken = AuthCredentials.AccessToken;
104
+ this._CampaignID = AuthCredentials.CampaignID;
105
+ }
106
+
107
+ private static async FetchAPI(url: string) {
108
+ if (!this._AccessToken || !this._CampaignID) {
109
+ throw new Error(
110
+ 'AccessToken and CampaignID are required on Authorization'
111
+ );
112
+ }
113
+
114
+ const resolvedUrl = url.startsWith('http') ? url : this._URL + url;
115
+
116
+ return await axios(resolvedUrl, {
117
+ method: 'GET',
118
+ headers: { Authorization: 'Bearer ' + this._AccessToken },
119
+ }).catch((err: Error) => {
120
+ throw new Error('Fetch API Failed...' + err);
121
+ });
122
+ }
123
+
124
+ private static BuildMembersURL(pageSize: number): string {
125
+ const params = new URLSearchParams({
126
+ include: 'user,currently_entitled_tiers',
127
+ 'page[count]': String(pageSize),
128
+ 'fields[member]':
129
+ 'currently_entitled_amount_cents,campaign_lifetime_support_cents,email,full_name,is_follower,last_charge_date,last_charge_status,next_charge_date,note,patron_status,pledge_relationship_start,will_pay_amount_cents',
130
+ 'fields[user]': 'social_connections',
131
+ 'fields[tier]': 'title',
132
+ });
133
+
134
+ return `${this._URL}campaigns/${this._CampaignID}/members?${params.toString()}`;
135
+ }
136
+
137
+ public static async FetchPatrons(
138
+ filters: Array<PatronStatus> = ['active_patron'],
139
+ pageSize: number = 450,
140
+ showSandboxPatrons: boolean = false
141
+ ) {
142
+ const Patrons: Array<PatronType> = [];
143
+ let nextUrl: string | null = this.BuildMembersURL(pageSize);
144
+
145
+ while (nextUrl) {
146
+ const { data } = await this.FetchAPI(nextUrl);
147
+ const PagePatrons = data?.data || [];
148
+
149
+ for (let x = 0; x < PagePatrons.length; x++) {
150
+ const Relationships = PagePatrons[x].relationships;
151
+ const Attributes = PagePatrons[x].attributes;
152
+
153
+ if (!filters.includes(Attributes.patron_status)) continue;
154
+
155
+ const socialInfo = data.included?.find(
156
+ (item: any) =>
157
+ item.id === Relationships.user.data.id &&
158
+ item.type === 'user'
159
+ );
160
+
161
+ const tierInfo = data.included?.find(
162
+ (item: any) =>
163
+ item.id ===
164
+ Relationships.currently_entitled_tiers?.data[0]
165
+ ?.id && item.type === 'tier'
166
+ );
167
+
168
+ const discordId =
169
+ socialInfo?.attributes?.social_connections?.discord
170
+ ?.user_id ?? null;
171
+
172
+ Patrons.push({
173
+ displayId: Relationships.user.data.id,
174
+ displayName: Attributes.full_name ?? null,
175
+ emailAddress: Attributes.email ?? null,
176
+ isFollower: Attributes.is_follower,
177
+ subscription: {
178
+ note: Attributes.note,
179
+ currentEntitled: {
180
+ status: Attributes.patron_status,
181
+ lastChargeStatus:
182
+ Attributes.last_charge_status ?? null,
183
+ tier: {
184
+ id: tierInfo ? tierInfo.id : null,
185
+ title: tierInfo
186
+ ? tierInfo.attributes.title
187
+ : null,
188
+ },
189
+ cents:
190
+ Attributes.currently_entitled_amount_cents !== 0
191
+ ? Attributes.currently_entitled_amount_cents
192
+ : null,
193
+ willPayCents: Attributes.will_pay_amount_cents,
194
+ lifetimeCents:
195
+ Attributes.campaign_lifetime_support_cents,
196
+ firstCharge: Attributes.pledge_relationship_start,
197
+ nextCharge: Attributes.next_charge_date,
198
+ lastCharge: Attributes.last_charge_date,
199
+ },
200
+ },
201
+ mediaConnection: {
202
+ patreon: {
203
+ id: Relationships.user.data.id,
204
+ url: Relationships.user.links.related,
205
+ },
206
+ discord: {
207
+ id: discordId,
208
+ url: discordId
209
+ ? `https://discord.com/users/${discordId}`
210
+ : null,
211
+ },
212
+ },
213
+ });
214
+ }
215
+
216
+ nextUrl = data?.links?.next ?? null;
217
+ }
218
+
219
+ if (showSandboxPatrons) {
220
+ for (let x = 0; x < this._SandboxPatrons.length; x++) {
221
+ const Patron = this._SandboxPatrons[x];
222
+
223
+ Patrons.push({
224
+ displayId: Patron.displayId,
225
+ displayName: Patron.displayName,
226
+ emailAddress: Patron.emailAddress,
227
+ isFollower: false,
228
+ subscription: {
229
+ note: 'Sandbox',
230
+ currentEntitled: {
231
+ status: Patron.patronStatus,
232
+ lastChargeStatus: null,
233
+ tier: {
234
+ id: Patron.tier.id,
235
+ title: Patron.tier.title,
236
+ },
237
+ cents: Patron.cents,
238
+ willPayCents: Patron.willPayCents,
239
+ lifetimeCents: Patron.lifetimeCents,
240
+ firstCharge: Patron.firstCharge,
241
+ nextCharge: Patron.nextCharge,
242
+ lastCharge: Patron.lastCharge,
243
+ },
244
+ },
245
+ mediaConnection: {
246
+ patreon: {
247
+ id: Patron.mediaConnection.patreon.id,
248
+ url: Patron.mediaConnection.patreon.url,
249
+ },
250
+ discord: {
251
+ id: Patron.mediaConnection.discord?.id ?? null,
252
+ url: Patron.mediaConnection.discord?.url ?? null,
253
+ },
254
+ },
255
+ });
256
+ }
257
+ }
258
+
259
+ return Patrons;
260
+ }
261
+
262
+ protected static _SandboxAddPatron(Patron: SandboxOptions) {
263
+ this._SandboxPatrons.push(Patron);
264
+ }
265
+
266
+ protected static _SandboxGetPatron() {
267
+ return this._SandboxPatrons;
268
+ }
269
+
270
+ // public static FetchPatron() {}
271
+
272
+ public static async FetchCampaign(): Promise<CampaignType> {
273
+ const params = new URLSearchParams({
274
+ 'fields[campaign]':
275
+ 'name,currency,patron_count,is_monthly,is_nsfw,summary,created_at,published_at,image_url,image_small_url,discord_server_id',
276
+ });
277
+
278
+ const { data } = await this.FetchAPI(
279
+ `campaigns/${this._CampaignID}?${params.toString()}`
280
+ );
281
+
282
+ const Attributes = data?.data?.attributes;
283
+
284
+ return {
285
+ id: data?.data?.id,
286
+ name: Attributes.name,
287
+ patronCount: Attributes.patron_count,
288
+ currency: Attributes.currency,
289
+ isMonthly: Attributes.is_monthly,
290
+ isNsfw: Attributes.is_nsfw,
291
+ summary: Attributes.summary ?? null,
292
+ createdAt: Attributes.created_at,
293
+ publishedAt: Attributes.published_at ?? null,
294
+ imageUrl: Attributes.image_url ?? null,
295
+ imageSmallUrl: Attributes.image_small_url ?? null,
296
+ discordServerId: Attributes.discord_server_id ?? null,
297
+ };
298
+ }
299
+ }
300
+
301
+ export class Sandbox extends Patreon {
302
+ public static GetPatrons() {
303
+ return super._SandboxGetPatron();
304
+ }
305
+
306
+ public static AddPatron(Patron: SandboxOptions) {
307
+ super._SandboxAddPatron(Patron);
308
+ }
309
+ }