mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-08 22:33:26 +01:00
[AltStoreCore] Refactors PatreonAPI to reduce duplicate logic
This commit is contained in:
@@ -182,9 +182,9 @@
|
|||||||
BF66EE8C2501AEB2007EE018 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE8B2501AEB1007EE018 /* Keychain.swift */; };
|
BF66EE8C2501AEB2007EE018 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE8B2501AEB1007EE018 /* Keychain.swift */; };
|
||||||
BF66EE942501AEBC007EE018 /* ALTAppPermissions.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE8E2501AEBC007EE018 /* ALTAppPermissions.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
BF66EE942501AEBC007EE018 /* ALTAppPermissions.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE8E2501AEBC007EE018 /* ALTAppPermissions.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
BF66EE952501AEBC007EE018 /* ALTSourceUserInfoKey.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE8F2501AEBC007EE018 /* ALTSourceUserInfoKey.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
BF66EE952501AEBC007EE018 /* ALTSourceUserInfoKey.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE8F2501AEBC007EE018 /* ALTSourceUserInfoKey.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
BF66EE962501AEBC007EE018 /* ALTPatreonBenefitType.m in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE902501AEBC007EE018 /* ALTPatreonBenefitType.m */; };
|
BF66EE962501AEBC007EE018 /* ALTPatreonBenefitID.m in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE902501AEBC007EE018 /* ALTPatreonBenefitID.m */; };
|
||||||
BF66EE972501AEBC007EE018 /* ALTAppPermissions.m in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE912501AEBC007EE018 /* ALTAppPermissions.m */; };
|
BF66EE972501AEBC007EE018 /* ALTAppPermissions.m in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE912501AEBC007EE018 /* ALTAppPermissions.m */; };
|
||||||
BF66EE982501AEBC007EE018 /* ALTPatreonBenefitType.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE922501AEBC007EE018 /* ALTPatreonBenefitType.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
BF66EE982501AEBC007EE018 /* ALTPatreonBenefitID.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE922501AEBC007EE018 /* ALTPatreonBenefitID.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
BF66EE992501AEBC007EE018 /* ALTSourceUserInfoKey.m in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE932501AEBC007EE018 /* ALTSourceUserInfoKey.m */; };
|
BF66EE992501AEBC007EE018 /* ALTSourceUserInfoKey.m in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE932501AEBC007EE018 /* ALTSourceUserInfoKey.m */; };
|
||||||
BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE9B2501AEC1007EE018 /* AppProtocol.swift */; };
|
BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE9B2501AEC1007EE018 /* AppProtocol.swift */; };
|
||||||
BF66EE9E2501AEC1007EE018 /* Fetchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE9C2501AEC1007EE018 /* Fetchable.swift */; };
|
BF66EE9E2501AEC1007EE018 /* Fetchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE9C2501AEC1007EE018 /* Fetchable.swift */; };
|
||||||
@@ -417,6 +417,8 @@
|
|||||||
D5A299872AAB9E4E00A3988D /* ProcessError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A1B2AA284ED00EF863D /* ProcessError.swift */; };
|
D5A299872AAB9E4E00A3988D /* ProcessError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A1B2AA284ED00EF863D /* ProcessError.swift */; };
|
||||||
D5A299882AAB9E4E00A3988D /* JITError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A1D2E32AA50EB60066CACC /* JITError.swift */; };
|
D5A299882AAB9E4E00A3988D /* JITError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A1D2E32AA50EB60066CACC /* JITError.swift */; };
|
||||||
D5A299892AAB9E5900A3988D /* AppProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59A6B7D2AA9226C00F61259 /* AppProcess.swift */; };
|
D5A299892AAB9E5900A3988D /* AppProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59A6B7D2AA9226C00F61259 /* AppProcess.swift */; };
|
||||||
|
D5A645232AF5B5C50047D980 /* PatreonAPI+Responses.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A645222AF5B5C50047D980 /* PatreonAPI+Responses.swift */; };
|
||||||
|
D5A645252AF5BC7F0047D980 /* UserAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A645242AF5BC7F0047D980 /* UserAccount.swift */; };
|
||||||
D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */; };
|
D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */; };
|
||||||
D5B6F6A92AD75D01007EED5A /* ProcessInfo+Previews.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6F6A82AD75D01007EED5A /* ProcessInfo+Previews.swift */; };
|
D5B6F6A92AD75D01007EED5A /* ProcessInfo+Previews.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6F6A82AD75D01007EED5A /* ProcessInfo+Previews.swift */; };
|
||||||
D5B6F6AB2AD76541007EED5A /* PreviewAppScreenshotsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6F6AA2AD76541007EED5A /* PreviewAppScreenshotsViewController.swift */; };
|
D5B6F6AB2AD76541007EED5A /* PreviewAppScreenshotsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6F6AA2AD76541007EED5A /* PreviewAppScreenshotsViewController.swift */; };
|
||||||
@@ -861,9 +863,9 @@
|
|||||||
BF66EE8B2501AEB1007EE018 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
BF66EE8B2501AEB1007EE018 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
||||||
BF66EE8E2501AEBC007EE018 /* ALTAppPermissions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ALTAppPermissions.h; sourceTree = "<group>"; };
|
BF66EE8E2501AEBC007EE018 /* ALTAppPermissions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ALTAppPermissions.h; sourceTree = "<group>"; };
|
||||||
BF66EE8F2501AEBC007EE018 /* ALTSourceUserInfoKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ALTSourceUserInfoKey.h; sourceTree = "<group>"; };
|
BF66EE8F2501AEBC007EE018 /* ALTSourceUserInfoKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ALTSourceUserInfoKey.h; sourceTree = "<group>"; };
|
||||||
BF66EE902501AEBC007EE018 /* ALTPatreonBenefitType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ALTPatreonBenefitType.m; sourceTree = "<group>"; };
|
BF66EE902501AEBC007EE018 /* ALTPatreonBenefitID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ALTPatreonBenefitID.m; sourceTree = "<group>"; };
|
||||||
BF66EE912501AEBC007EE018 /* ALTAppPermissions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ALTAppPermissions.m; sourceTree = "<group>"; };
|
BF66EE912501AEBC007EE018 /* ALTAppPermissions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ALTAppPermissions.m; sourceTree = "<group>"; };
|
||||||
BF66EE922501AEBC007EE018 /* ALTPatreonBenefitType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ALTPatreonBenefitType.h; sourceTree = "<group>"; };
|
BF66EE922501AEBC007EE018 /* ALTPatreonBenefitID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ALTPatreonBenefitID.h; sourceTree = "<group>"; };
|
||||||
BF66EE932501AEBC007EE018 /* ALTSourceUserInfoKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ALTSourceUserInfoKey.m; sourceTree = "<group>"; };
|
BF66EE932501AEBC007EE018 /* ALTSourceUserInfoKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ALTSourceUserInfoKey.m; sourceTree = "<group>"; };
|
||||||
BF66EE9B2501AEC1007EE018 /* AppProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppProtocol.swift; sourceTree = "<group>"; };
|
BF66EE9B2501AEC1007EE018 /* AppProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppProtocol.swift; sourceTree = "<group>"; };
|
||||||
BF66EE9C2501AEC1007EE018 /* Fetchable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fetchable.swift; sourceTree = "<group>"; };
|
BF66EE9C2501AEC1007EE018 /* Fetchable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fetchable.swift; sourceTree = "<group>"; };
|
||||||
@@ -1085,6 +1087,8 @@
|
|||||||
D5A1D2E82AA512940066CACC /* RemoteServiceDiscoveryTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteServiceDiscoveryTunnel.swift; sourceTree = "<group>"; };
|
D5A1D2E82AA512940066CACC /* RemoteServiceDiscoveryTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteServiceDiscoveryTunnel.swift; sourceTree = "<group>"; };
|
||||||
D5A1D2EA2AA513410066CACC /* URL+Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Tools.swift"; sourceTree = "<group>"; };
|
D5A1D2EA2AA513410066CACC /* URL+Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Tools.swift"; sourceTree = "<group>"; };
|
||||||
D5A2193329B14F94002229FC /* DeprecatedAPIs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeprecatedAPIs.swift; sourceTree = "<group>"; };
|
D5A2193329B14F94002229FC /* DeprecatedAPIs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeprecatedAPIs.swift; sourceTree = "<group>"; };
|
||||||
|
D5A645222AF5B5C50047D980 /* PatreonAPI+Responses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PatreonAPI+Responses.swift"; sourceTree = "<group>"; };
|
||||||
|
D5A645242AF5BC7F0047D980 /* UserAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAccount.swift; sourceTree = "<group>"; };
|
||||||
D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearAppCacheOperation.swift; sourceTree = "<group>"; };
|
D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearAppCacheOperation.swift; sourceTree = "<group>"; };
|
||||||
D5B6F6A82AD75D01007EED5A /* ProcessInfo+Previews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+Previews.swift"; sourceTree = "<group>"; };
|
D5B6F6A82AD75D01007EED5A /* ProcessInfo+Previews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+Previews.swift"; sourceTree = "<group>"; };
|
||||||
D5B6F6AA2AD76541007EED5A /* PreviewAppScreenshotsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewAppScreenshotsViewController.swift; sourceTree = "<group>"; };
|
D5B6F6AA2AD76541007EED5A /* PreviewAppScreenshotsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewAppScreenshotsViewController.swift; sourceTree = "<group>"; };
|
||||||
@@ -1639,8 +1643,8 @@
|
|||||||
D5893F812A141E4900E767CD /* KnownSource.swift */,
|
D5893F812A141E4900E767CD /* KnownSource.swift */,
|
||||||
BF66EE8E2501AEBC007EE018 /* ALTAppPermissions.h */,
|
BF66EE8E2501AEBC007EE018 /* ALTAppPermissions.h */,
|
||||||
BF66EE912501AEBC007EE018 /* ALTAppPermissions.m */,
|
BF66EE912501AEBC007EE018 /* ALTAppPermissions.m */,
|
||||||
BF66EE922501AEBC007EE018 /* ALTPatreonBenefitType.h */,
|
BF66EE922501AEBC007EE018 /* ALTPatreonBenefitID.h */,
|
||||||
BF66EE902501AEBC007EE018 /* ALTPatreonBenefitType.m */,
|
BF66EE902501AEBC007EE018 /* ALTPatreonBenefitID.m */,
|
||||||
BF66EE8F2501AEBC007EE018 /* ALTSourceUserInfoKey.h */,
|
BF66EE8F2501AEBC007EE018 /* ALTSourceUserInfoKey.h */,
|
||||||
BF66EE932501AEBC007EE018 /* ALTSourceUserInfoKey.m */,
|
BF66EE932501AEBC007EE018 /* ALTSourceUserInfoKey.m */,
|
||||||
);
|
);
|
||||||
@@ -1661,11 +1665,13 @@
|
|||||||
BF66EE9F2501AEC5007EE018 /* Patreon */ = {
|
BF66EE9F2501AEC5007EE018 /* Patreon */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
BF66EEA12501AEC5007EE018 /* PatreonAPI.swift */,
|
||||||
|
D5A645222AF5B5C50047D980 /* PatreonAPI+Responses.swift */,
|
||||||
BF66EEA02501AEC5007EE018 /* Benefit.swift */,
|
BF66EEA02501AEC5007EE018 /* Benefit.swift */,
|
||||||
BF66EEA22501AEC5007EE018 /* Campaign.swift */,
|
BF66EEA22501AEC5007EE018 /* Campaign.swift */,
|
||||||
BF66EEA12501AEC5007EE018 /* PatreonAPI.swift */,
|
|
||||||
BF66EEA32501AEC5007EE018 /* Patron.swift */,
|
BF66EEA32501AEC5007EE018 /* Patron.swift */,
|
||||||
BF66EEA42501AEC5007EE018 /* Tier.swift */,
|
BF66EEA42501AEC5007EE018 /* Tier.swift */,
|
||||||
|
D5A645242AF5BC7F0047D980 /* UserAccount.swift */,
|
||||||
);
|
);
|
||||||
path = Patreon;
|
path = Patreon;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2390,7 +2396,7 @@
|
|||||||
files = (
|
files = (
|
||||||
BF66EE822501AE50007EE018 /* AltStoreCore.h in Headers */,
|
BF66EE822501AE50007EE018 /* AltStoreCore.h in Headers */,
|
||||||
BF66EE952501AEBC007EE018 /* ALTSourceUserInfoKey.h in Headers */,
|
BF66EE952501AEBC007EE018 /* ALTSourceUserInfoKey.h in Headers */,
|
||||||
BF66EE982501AEBC007EE018 /* ALTPatreonBenefitType.h in Headers */,
|
BF66EE982501AEBC007EE018 /* ALTPatreonBenefitID.h in Headers */,
|
||||||
BFAECC5F2501B0BF00528F27 /* ALTConstants.h in Headers */,
|
BFAECC5F2501B0BF00528F27 /* ALTConstants.h in Headers */,
|
||||||
BFAECC5D2501B0BF00528F27 /* ALTConnection.h in Headers */,
|
BFAECC5D2501B0BF00528F27 /* ALTConnection.h in Headers */,
|
||||||
BF66EE942501AEBC007EE018 /* ALTAppPermissions.h in Headers */,
|
BF66EE942501AEBC007EE018 /* ALTAppPermissions.h in Headers */,
|
||||||
@@ -3075,6 +3081,7 @@
|
|||||||
BF66EED32501AECA007EE018 /* AltStore2ToAltStore3.xcmappingmodel in Sources */,
|
BF66EED32501AECA007EE018 /* AltStore2ToAltStore3.xcmappingmodel in Sources */,
|
||||||
BF66EEA52501AEC5007EE018 /* Benefit.swift in Sources */,
|
BF66EEA52501AEC5007EE018 /* Benefit.swift in Sources */,
|
||||||
BF66EED22501AECA007EE018 /* AltStore4ToAltStore5.xcmappingmodel in Sources */,
|
BF66EED22501AECA007EE018 /* AltStore4ToAltStore5.xcmappingmodel in Sources */,
|
||||||
|
D5A645252AF5BC7F0047D980 /* UserAccount.swift in Sources */,
|
||||||
D5189C022A01BC6800F44625 /* UserInfoValue.swift in Sources */,
|
D5189C022A01BC6800F44625 /* UserInfoValue.swift in Sources */,
|
||||||
BFAECC5B2501B0A400528F27 /* Bundle+AltStore.swift in Sources */,
|
BFAECC5B2501B0A400528F27 /* Bundle+AltStore.swift in Sources */,
|
||||||
BF66EECD2501AECA007EE018 /* StoreAppPolicy.swift in Sources */,
|
BF66EECD2501AECA007EE018 /* StoreAppPolicy.swift in Sources */,
|
||||||
@@ -3114,7 +3121,7 @@
|
|||||||
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */,
|
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */,
|
||||||
D51AD27F29356B7B00967AAA /* ALTWrappedError.m in Sources */,
|
D51AD27F29356B7B00967AAA /* ALTWrappedError.m in Sources */,
|
||||||
BF989184250AACFC002ACF50 /* Date+RelativeDate.swift in Sources */,
|
BF989184250AACFC002ACF50 /* Date+RelativeDate.swift in Sources */,
|
||||||
BF66EE962501AEBC007EE018 /* ALTPatreonBenefitType.m in Sources */,
|
BF66EE962501AEBC007EE018 /* ALTPatreonBenefitID.m in Sources */,
|
||||||
BFAECC5A2501B0A400528F27 /* NetworkConnection.swift in Sources */,
|
BFAECC5A2501B0A400528F27 /* NetworkConnection.swift in Sources */,
|
||||||
D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */,
|
D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */,
|
||||||
BF66EEE92501AED0007EE018 /* JSONDecoder+Properties.swift in Sources */,
|
BF66EEE92501AED0007EE018 /* JSONDecoder+Properties.swift in Sources */,
|
||||||
@@ -3147,6 +3154,7 @@
|
|||||||
BF66EED72501AECA007EE018 /* InstalledApp.swift in Sources */,
|
BF66EED72501AECA007EE018 /* InstalledApp.swift in Sources */,
|
||||||
0EE7FDC92BE8D07400D1E390 /* NSError+AltStore.swift in Sources */,
|
0EE7FDC92BE8D07400D1E390 /* NSError+AltStore.swift in Sources */,
|
||||||
D5DB145A28F9DC5A00A8F606 /* ALTLocalizedError.swift in Sources */,
|
D5DB145A28F9DC5A00A8F606 /* ALTLocalizedError.swift in Sources */,
|
||||||
|
D5A645232AF5B5C50047D980 /* PatreonAPI+Responses.swift in Sources */,
|
||||||
D5185B822AE1E71D00646E33 /* Source13To14MigrationPolicy.swift in Sources */,
|
D5185B822AE1E71D00646E33 /* Source13To14MigrationPolicy.swift in Sources */,
|
||||||
BF66EECE2501AECA007EE018 /* InstalledAppPolicy.swift in Sources */,
|
BF66EECE2501AECA007EE018 /* InstalledAppPolicy.swift in Sources */,
|
||||||
BF1FE359251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
BF1FE359251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ FOUNDATION_EXPORT const unsigned char AltStoreCoreVersionString[];
|
|||||||
|
|
||||||
#import <AltStoreCore/ALTAppPermissions.h>
|
#import <AltStoreCore/ALTAppPermissions.h>
|
||||||
#import <AltStoreCore/ALTSourceUserInfoKey.h>
|
#import <AltStoreCore/ALTSourceUserInfoKey.h>
|
||||||
#import <AltStoreCore/ALTPatreonBenefitType.h>
|
#import <AltStoreCore/ALTPatreonBenefitID.h>
|
||||||
|
|
||||||
// Shared
|
// Shared
|
||||||
#import <AltStoreCore/ALTConstants.h>
|
#import <AltStoreCore/ALTConstants.h>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class ManagedPatron: NSManagedObject, Fetchable
|
|||||||
super.init(entity: entity, insertInto: context)
|
super.init(entity: entity, insertInto: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init?(patron: Patron, context: NSManagedObjectContext)
|
public init?(patron: PatreonAPI.Patron, context: NSManagedObjectContext)
|
||||||
{
|
{
|
||||||
// Only cache Patrons with non-nil names.
|
// Only cache Patrons with non-nil names.
|
||||||
guard let name = patron.name else { return nil }
|
guard let name = patron.name else { return nil }
|
||||||
|
|||||||
@@ -8,27 +8,6 @@
|
|||||||
|
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
extension PatreonAPI
|
|
||||||
{
|
|
||||||
struct AccountResponse: Decodable
|
|
||||||
{
|
|
||||||
struct Data: Decodable
|
|
||||||
{
|
|
||||||
struct Attributes: Decodable
|
|
||||||
{
|
|
||||||
var first_name: String?
|
|
||||||
var full_name: String
|
|
||||||
}
|
|
||||||
|
|
||||||
var id: String
|
|
||||||
var attributes: Attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
var data: Data
|
|
||||||
var included: [PatronResponse]?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc(PatreonAccount)
|
@objc(PatreonAccount)
|
||||||
public class PatreonAccount: NSManagedObject, Fetchable
|
public class PatreonAccount: NSManagedObject, Fetchable
|
||||||
{
|
{
|
||||||
@@ -44,13 +23,13 @@ public class PatreonAccount: NSManagedObject, Fetchable
|
|||||||
super.init(entity: entity, insertInto: context)
|
super.init(entity: entity, insertInto: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(response: PatreonAPI.AccountResponse, context: NSManagedObjectContext)
|
init(account: PatreonAPI.UserAccount, context: NSManagedObjectContext)
|
||||||
{
|
{
|
||||||
super.init(entity: PatreonAccount.entity(), insertInto: context)
|
super.init(entity: PatreonAccount.entity(), insertInto: context)
|
||||||
|
|
||||||
self.identifier = response.data.id
|
self.identifier = account.identifier
|
||||||
self.name = response.data.attributes.full_name
|
self.name = account.name
|
||||||
self.firstName = response.data.attributes.first_name
|
self.firstName = account.firstName
|
||||||
|
|
||||||
// if let patronResponse = response.included?.first
|
// if let patronResponse = response.included?.first
|
||||||
// {
|
// {
|
||||||
|
|||||||
@@ -10,18 +10,25 @@ import Foundation
|
|||||||
|
|
||||||
extension PatreonAPI
|
extension PatreonAPI
|
||||||
{
|
{
|
||||||
struct BenefitResponse: Decodable
|
typealias BenefitResponse = DataResponse<BenefitAttributes, AnyRelationships>
|
||||||
|
|
||||||
|
struct BenefitAttributes: Decodable
|
||||||
{
|
{
|
||||||
var id: String
|
var title: String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Benefit: Hashable
|
extension PatreonAPI
|
||||||
{
|
{
|
||||||
public var type: ALTPatreonBenefitType
|
public struct Benefit: Hashable
|
||||||
|
|
||||||
init(response: PatreonAPI.BenefitResponse)
|
|
||||||
{
|
{
|
||||||
self.type = ALTPatreonBenefitType(response.id)
|
public var name: String
|
||||||
|
public var identifier: ALTPatreonBenefitID
|
||||||
|
|
||||||
|
internal init(response: BenefitResponse)
|
||||||
|
{
|
||||||
|
self.name = response.attributes.title
|
||||||
|
self.identifier = ALTPatreonBenefitID(response.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,25 @@ import Foundation
|
|||||||
|
|
||||||
extension PatreonAPI
|
extension PatreonAPI
|
||||||
{
|
{
|
||||||
struct CampaignResponse: Decodable
|
typealias CampaignResponse = DataResponse<CampaignAttributes, AnyRelationships>
|
||||||
|
|
||||||
|
struct CampaignAttributes: Decodable
|
||||||
{
|
{
|
||||||
var id: String
|
var url: URL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Campaign
|
extension PatreonAPI
|
||||||
{
|
{
|
||||||
public var identifier: String
|
public struct Campaign
|
||||||
|
|
||||||
init(response: PatreonAPI.CampaignResponse)
|
|
||||||
{
|
{
|
||||||
self.identifier = response.id
|
public var identifier: String
|
||||||
|
public var url: URL
|
||||||
|
|
||||||
|
internal init(response: PatreonAPI.CampaignResponse)
|
||||||
|
{
|
||||||
|
self.identifier = response.id
|
||||||
|
self.url = response.attributes.url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
161
AltStoreCore/Patreon/PatreonAPI+Responses.swift
Normal file
161
AltStoreCore/Patreon/PatreonAPI+Responses.swift
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
//
|
||||||
|
// PatreonAPI+Responses.swift
|
||||||
|
// AltStoreCore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 11/3/23.
|
||||||
|
// Copyright © 2023 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
protocol ResponseData: Decodable
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows us to use Arrays with Response<> despite them not conforming to `ItemResponse`
|
||||||
|
extension Array: ResponseData where Element: ItemResponse
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol ItemResponse: ResponseData
|
||||||
|
{
|
||||||
|
var id: String { get }
|
||||||
|
var type: String { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PatreonAPI
|
||||||
|
{
|
||||||
|
struct Response<Data: ResponseData>: Decodable
|
||||||
|
{
|
||||||
|
var data: Data
|
||||||
|
|
||||||
|
var included: IncludedResponses?
|
||||||
|
var links: [String: URL]?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AnyItemResponse: ItemResponse
|
||||||
|
{
|
||||||
|
var id: String
|
||||||
|
var type: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DataResponse<Attributes: Decodable, Relationships: Decodable>: ItemResponse
|
||||||
|
{
|
||||||
|
var id: String
|
||||||
|
var type: String
|
||||||
|
|
||||||
|
var attributes: Attributes
|
||||||
|
var relationships: Relationships?
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Never` only conforms to Decodable from iOS 17 onwards,
|
||||||
|
// so use our own "Empty" type for DataResponses without relationships.
|
||||||
|
struct AnyRelationships: Decodable
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IncludedResponses: Decodable
|
||||||
|
{
|
||||||
|
var items: [IncludedItem]
|
||||||
|
|
||||||
|
var campaigns: [String: CampaignResponse]
|
||||||
|
var patrons: [String: PatronResponse]
|
||||||
|
var tiers: [String: TierResponse]
|
||||||
|
var benefits: [String: BenefitResponse]
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws
|
||||||
|
{
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
self.items = try container.decode([IncludedItem].self)
|
||||||
|
|
||||||
|
var campaignsByID = [String: PatreonAPI.CampaignResponse]()
|
||||||
|
var patronsByID = [String: PatreonAPI.PatronResponse]()
|
||||||
|
var tiersByID = [String: PatreonAPI.TierResponse]()
|
||||||
|
var benefitsByID = [String: PatreonAPI.BenefitResponse]()
|
||||||
|
|
||||||
|
for response in self.items
|
||||||
|
{
|
||||||
|
switch response
|
||||||
|
{
|
||||||
|
case .campaign(let response): campaignsByID[response.id] = response
|
||||||
|
case .patron(let response): patronsByID[response.id] = response
|
||||||
|
case .tier(let response): tiersByID[response.id] = response
|
||||||
|
case .benefit(let response): benefitsByID[response.id] = response
|
||||||
|
case .unknown: break // Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.campaigns = campaignsByID
|
||||||
|
self.patrons = patronsByID
|
||||||
|
self.tiers = tiersByID
|
||||||
|
self.benefits = benefitsByID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IncludedItem: ItemResponse
|
||||||
|
{
|
||||||
|
case tier(TierResponse)
|
||||||
|
case benefit(BenefitResponse)
|
||||||
|
case patron(PatronResponse)
|
||||||
|
case campaign(CampaignResponse)
|
||||||
|
case unknown(AnyItemResponse)
|
||||||
|
|
||||||
|
var id: String {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .tier(let response): return response.id
|
||||||
|
case .benefit(let response): return response.id
|
||||||
|
case .patron(let response): return response.id
|
||||||
|
case .campaign(let response): return response.id
|
||||||
|
case .unknown(let response): return response.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var type: String {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .tier(let response): return response.type
|
||||||
|
case .benefit(let response): return response.type
|
||||||
|
case .patron(let response): return response.type
|
||||||
|
case .campaign(let response): return response.type
|
||||||
|
case .unknown(let response): return response.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey
|
||||||
|
{
|
||||||
|
case type
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws
|
||||||
|
{
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
let type = try container.decode(String.self, forKey: .type)
|
||||||
|
switch type
|
||||||
|
{
|
||||||
|
case "tier":
|
||||||
|
let response = try TierResponse(from: decoder)
|
||||||
|
self = .tier(response)
|
||||||
|
|
||||||
|
case "benefit":
|
||||||
|
let response = try BenefitResponse(from: decoder)
|
||||||
|
self = .benefit(response)
|
||||||
|
|
||||||
|
case "member":
|
||||||
|
let response = try PatronResponse(from: decoder)
|
||||||
|
self = .patron(response)
|
||||||
|
|
||||||
|
case "campaign":
|
||||||
|
let response = try CampaignResponse(from: decoder)
|
||||||
|
self = .campaign(response)
|
||||||
|
|
||||||
|
default:
|
||||||
|
Logger.main.error("Unrecognized PatreonAPI response type: \(type, privacy: .public).")
|
||||||
|
|
||||||
|
let response = try AnyItemResponse(from: decoder)
|
||||||
|
self = .unknown(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,61 +32,20 @@ enum PatreonAPIErrorCode: Int, ALTErrorEnum, CaseIterable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias PatreonAPIError = PatreonAPIErrorCode.Error
|
|
||||||
enum PatreonAPIErrorCode: Int, ALTErrorEnum, CaseIterable
|
|
||||||
{
|
|
||||||
case unknown
|
|
||||||
case notAuthenticated
|
|
||||||
case invalidAccessToken
|
|
||||||
|
|
||||||
var errorFailureReason: String {
|
|
||||||
switch self
|
|
||||||
{
|
|
||||||
case .unknown: return NSLocalizedString("An unknown error occurred.", comment: "")
|
|
||||||
case .notAuthenticated: return NSLocalizedString("No connected Patreon account.", comment: "")
|
|
||||||
case .invalidAccessToken: return NSLocalizedString("Invalid access token.", comment: "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PatreonAPI
|
extension PatreonAPI
|
||||||
{
|
{
|
||||||
|
static let altstoreCampaignID = "2863968"
|
||||||
|
|
||||||
|
typealias FetchAccountResponse = Response<UserAccountResponse>
|
||||||
|
typealias FriendZonePatronsResponse = Response<[PatronResponse]>
|
||||||
|
|
||||||
enum AuthorizationType
|
enum AuthorizationType
|
||||||
{
|
{
|
||||||
case none
|
case none
|
||||||
case user
|
case user
|
||||||
case creator
|
case creator
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AnyResponse: Decodable
|
|
||||||
{
|
|
||||||
case tier(TierResponse)
|
|
||||||
case benefit(BenefitResponse)
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey
|
|
||||||
{
|
|
||||||
case type
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws
|
|
||||||
{
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
|
|
||||||
let type = try container.decode(String.self, forKey: .type)
|
|
||||||
switch type
|
|
||||||
{
|
|
||||||
case "tier":
|
|
||||||
let tier = try TierResponse(from: decoder)
|
|
||||||
self = .tier(tier)
|
|
||||||
|
|
||||||
case "benefit":
|
|
||||||
let benefit = try BenefitResponse(from: decoder)
|
|
||||||
self = .benefit(benefit)
|
|
||||||
|
|
||||||
default: throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Unrecognized Patreon response type.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PatreonAPI: NSObject
|
public class PatreonAPI: NSObject
|
||||||
@@ -155,14 +114,17 @@ public extension PatreonAPI
|
|||||||
func fetchAccount(completion: @escaping (Result<PatreonAccount, Swift.Error>) -> Void)
|
func fetchAccount(completion: @escaping (Result<PatreonAccount, Swift.Error>) -> Void)
|
||||||
{
|
{
|
||||||
var components = URLComponents(string: "/api/oauth2/v2/identity")!
|
var components = URLComponents(string: "/api/oauth2/v2/identity")!
|
||||||
components.queryItems = [URLQueryItem(name: "include", value: "memberships"),
|
components.queryItems = [URLQueryItem(name: "include", value: "memberships.campaign.tiers,memberships.currently_entitled_tiers.benefits"),
|
||||||
URLQueryItem(name: "fields[user]", value: "first_name,full_name"),
|
URLQueryItem(name: "fields[user]", value: "first_name,full_name"),
|
||||||
URLQueryItem(name: "fields[member]", value: "full_name,patron_status")]
|
URLQueryItem(name: "fields[member]", value: "full_name,patron_status")]
|
||||||
|
URLQueryItem(name: "fields[tier]", value: "title"),
|
||||||
|
URLQueryItem(name: "fields[benefit]", value: "title"),
|
||||||
|
URLQueryItem(name: "fields[campaign]", value: "url"),
|
||||||
|
|
||||||
let requestURL = components.url(relativeTo: self.baseURL)!
|
let requestURL = components.url(relativeTo: self.baseURL)!
|
||||||
let request = URLRequest(url: requestURL)
|
let request = URLRequest(url: requestURL)
|
||||||
|
|
||||||
self.send(request, authorizationType: .user) { (result: Result<AccountResponse, Swift.Error>) in
|
self.send(request, authorizationType: .user) { (result: Result<FetchAccountResponse, Swift.Error>) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(~PatreonAPIErrorCode.notAuthenticated):
|
case .failure(~PatreonAPIErrorCode.notAuthenticated):
|
||||||
@@ -170,10 +132,15 @@ public extension PatreonAPI
|
|||||||
completion(.failure(PatreonAPIError(.notAuthenticated)))
|
completion(.failure(PatreonAPIError(.notAuthenticated)))
|
||||||
}
|
}
|
||||||
|
|
||||||
case .failure(let error): completion(.failure(error))
|
case .failure(let error as NSError):
|
||||||
|
Logger.main.error("Failed to fetch Patreon account. \(error.localizedDebugDescription ?? error.localizedDescription, privacy: .public)")
|
||||||
|
completion(.failure(error))
|
||||||
|
|
||||||
case .success(let response):
|
case .success(let response):
|
||||||
|
let account = PatreonAPI.UserAccount(response: response.data, including: response.included)
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
let account = PatreonAccount(response: response, context: context)
|
let account = PatreonAccount(account: account, context: context)
|
||||||
Keychain.shared.patreonAccountID = account.identifier
|
Keychain.shared.patreonAccountID = account.identifier
|
||||||
completion(.success(account))
|
completion(.success(account))
|
||||||
}
|
}
|
||||||
@@ -183,57 +150,34 @@ public extension PatreonAPI
|
|||||||
|
|
||||||
func fetchPatrons(completion: @escaping (Result<[Patron], Swift.Error>) -> Void)
|
func fetchPatrons(completion: @escaping (Result<[Patron], Swift.Error>) -> Void)
|
||||||
{
|
{
|
||||||
var components = URLComponents(string: "/api/oauth2/v2/campaigns/\(campaignID)/members")!
|
var components = URLComponents(string: "/api/oauth2/v2/campaigns/\(PatreonAPI.altstoreCampaignID)/members")!
|
||||||
components.queryItems = [URLQueryItem(name: "include", value: "currently_entitled_tiers,currently_entitled_tiers.benefits"),
|
components.queryItems = [URLQueryItem(name: "include", value: "currently_entitled_tiers,currently_entitled_tiers.benefits"),
|
||||||
URLQueryItem(name: "fields[tier]", value: "title"),
|
URLQueryItem(name: "fields[tier]", value: "title"),
|
||||||
|
URLQueryItem(name: "fields[benefit]", value: "title"),
|
||||||
URLQueryItem(name: "fields[member]", value: "full_name,patron_status"),
|
URLQueryItem(name: "fields[member]", value: "full_name,patron_status"),
|
||||||
URLQueryItem(name: "page[size]", value: "1000")]
|
URLQueryItem(name: "page[size]", value: "1000")]
|
||||||
|
|
||||||
let requestURL = components.url(relativeTo: self.baseURL)!
|
let requestURL = components.url(relativeTo: self.baseURL)!
|
||||||
|
|
||||||
struct Response: Decodable
|
|
||||||
{
|
|
||||||
var data: [PatronResponse]
|
|
||||||
var included: [AnyResponse]
|
|
||||||
var links: [String: URL]?
|
|
||||||
}
|
|
||||||
|
|
||||||
var allPatrons = [Patron]()
|
var allPatrons = [Patron]()
|
||||||
|
|
||||||
func fetchPatrons(url: URL)
|
func fetchPatrons(url: URL)
|
||||||
{
|
{
|
||||||
let request = URLRequest(url: url)
|
let request = URLRequest(url: url)
|
||||||
|
|
||||||
self.send(request, authorizationType: .creator) { (result: Result<Response, Swift.Error>) in
|
self.send(request, authorizationType: .creator) { (result: Result<FriendZonePatronsResponse, Swift.Error>) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): completion(.failure(error))
|
case .failure(let error): completion(.failure(error))
|
||||||
case .success(let response):
|
case .success(let patronsResponse):
|
||||||
let tiers = response.included.compactMap { (response) -> Tier? in
|
let patrons = patronsResponse.data.map { (response) -> Patron in
|
||||||
switch response
|
let patron = Patron(response: response, including: patronsResponse.included)
|
||||||
{
|
|
||||||
case .tier(let tierResponse): return Tier(response: tierResponse)
|
|
||||||
case .benefit: return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tiersByIdentifier = Dictionary(tiers.map { ($0.identifier, $0) }, uniquingKeysWith: { (a, b) in return a })
|
|
||||||
|
|
||||||
let patrons = response.data.map { (response) -> Patron in
|
|
||||||
let patron = Patron(response: response)
|
|
||||||
|
|
||||||
for tierID in response.relationships?.currently_entitled_tiers.data ?? []
|
|
||||||
{
|
|
||||||
guard let tier = tiersByIdentifier[tierID.id] else { continue }
|
|
||||||
patron.benefits.formUnion(tier.benefits)
|
|
||||||
}
|
|
||||||
|
|
||||||
return patron
|
return patron
|
||||||
}.filter { $0.benefits.contains(where: { $0.type == .credits }) }
|
}.filter { $0.benefits.contains(where: { $0.identifier == .credits }) }
|
||||||
|
|
||||||
allPatrons.append(contentsOf: patrons)
|
allPatrons.append(contentsOf: patrons)
|
||||||
|
|
||||||
if let nextURL = response.links?["next"]
|
if let nextURL = patronsResponse.links?["next"]
|
||||||
{
|
{
|
||||||
fetchPatrons(url: nextURL)
|
fetchPatrons(url: nextURL)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,38 +10,22 @@ import Foundation
|
|||||||
|
|
||||||
extension PatreonAPI
|
extension PatreonAPI
|
||||||
{
|
{
|
||||||
struct PatronResponse: Decodable
|
typealias PatronResponse = DataResponse<PatronAttributes, PatronRelationships>
|
||||||
|
|
||||||
|
struct PatronAttributes: Decodable
|
||||||
{
|
{
|
||||||
struct Attributes: Decodable
|
var full_name: String?
|
||||||
{
|
var patron_status: String?
|
||||||
var full_name: String?
|
}
|
||||||
var patron_status: String?
|
|
||||||
}
|
struct PatronRelationships: Decodable
|
||||||
|
{
|
||||||
struct Relationships: Decodable
|
var campaign: Response<AnyItemResponse>?
|
||||||
{
|
var currently_entitled_tiers: Response<[AnyItemResponse]>?
|
||||||
struct Tiers: Decodable
|
|
||||||
{
|
|
||||||
struct TierID: Decodable
|
|
||||||
{
|
|
||||||
var id: String
|
|
||||||
var type: String
|
|
||||||
}
|
|
||||||
|
|
||||||
var data: [TierID]
|
|
||||||
}
|
|
||||||
|
|
||||||
var currently_entitled_tiers: Tiers
|
|
||||||
}
|
|
||||||
|
|
||||||
var id: String
|
|
||||||
var attributes: Attributes
|
|
||||||
|
|
||||||
var relationships: Relationships?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Patron
|
extension PatreonAPI
|
||||||
{
|
{
|
||||||
public enum Status: String, Decodable
|
public enum Status: String, Decodable
|
||||||
{
|
{
|
||||||
@@ -50,29 +34,46 @@ extension Patron
|
|||||||
case former = "former_patron"
|
case former = "former_patron"
|
||||||
case unknown = "unknown"
|
case unknown = "unknown"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class Patron
|
|
||||||
{
|
|
||||||
public var name: String?
|
|
||||||
public var identifier: String
|
|
||||||
|
|
||||||
public var status: Status
|
// Roughly equivalent to AltStoreCore.Pledge
|
||||||
|
public class Patron
|
||||||
public var benefits: Set<Benefit> = []
|
|
||||||
|
|
||||||
init(response: PatreonAPI.PatronResponse)
|
|
||||||
{
|
{
|
||||||
self.name = response.attributes.full_name
|
public var name: String?
|
||||||
self.identifier = response.id
|
public var identifier: String
|
||||||
|
public var status: Status
|
||||||
|
|
||||||
if let status = response.attributes.patron_status
|
// Relationships
|
||||||
|
public var campaign: Campaign?
|
||||||
|
public var tiers: Set<Tier> = []
|
||||||
|
public var benefits: Set<Benefit> = []
|
||||||
|
|
||||||
|
internal init(response: PatronResponse, including included: IncludedResponses?)
|
||||||
{
|
{
|
||||||
self.status = Status(rawValue: status) ?? .unknown
|
self.name = response.attributes.full_name
|
||||||
}
|
self.identifier = response.id
|
||||||
else
|
|
||||||
{
|
if let status = response.attributes.patron_status
|
||||||
self.status = .unknown
|
{
|
||||||
|
self.status = Status(rawValue: status) ?? .unknown
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.status = .unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let included, let relationships = response.relationships else { return }
|
||||||
|
|
||||||
|
if let campaignID = relationships.campaign?.data.id, let response = included.campaigns[campaignID]
|
||||||
|
{
|
||||||
|
let campaign = Campaign(response: response)
|
||||||
|
self.campaign = campaign
|
||||||
|
}
|
||||||
|
|
||||||
|
let tiers = (relationships.currently_entitled_tiers?.data ?? []).compactMap { included.tiers[$0.id] }.map { Tier(response: $0, including: included) }
|
||||||
|
self.tiers = Set(tiers)
|
||||||
|
|
||||||
|
let benefits = tiers.flatMap { $0.benefits }
|
||||||
|
self.benefits = Set(benefits)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,41 +10,38 @@ import Foundation
|
|||||||
|
|
||||||
extension PatreonAPI
|
extension PatreonAPI
|
||||||
{
|
{
|
||||||
struct TierResponse: Decodable
|
typealias TierResponse = DataResponse<TierAttributes, TierRelationships>
|
||||||
|
|
||||||
|
struct TierAttributes: Decodable
|
||||||
{
|
{
|
||||||
struct Attributes: Decodable
|
var title: String
|
||||||
{
|
}
|
||||||
var title: String
|
|
||||||
}
|
struct TierRelationships: Decodable
|
||||||
|
{
|
||||||
struct Relationships: Decodable
|
var benefits: Response<[AnyItemResponse]>?
|
||||||
{
|
|
||||||
struct Benefits: Decodable
|
|
||||||
{
|
|
||||||
var data: [BenefitResponse]
|
|
||||||
}
|
|
||||||
|
|
||||||
var benefits: Benefits
|
|
||||||
}
|
|
||||||
|
|
||||||
var id: String
|
|
||||||
var attributes: Attributes
|
|
||||||
|
|
||||||
var relationships: Relationships
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Tier
|
extension PatreonAPI
|
||||||
{
|
{
|
||||||
public var name: String
|
public struct Tier: Hashable
|
||||||
public var identifier: String
|
|
||||||
|
|
||||||
public var benefits: [Benefit] = []
|
|
||||||
|
|
||||||
init(response: PatreonAPI.TierResponse)
|
|
||||||
{
|
{
|
||||||
self.name = response.attributes.title
|
public var name: String
|
||||||
self.identifier = response.id
|
public var identifier: String
|
||||||
self.benefits = response.relationships.benefits.data.map(Benefit.init(response:))
|
|
||||||
|
// Relationships
|
||||||
|
public var benefits: [Benefit] = []
|
||||||
|
|
||||||
|
internal init(response: TierResponse, including included: IncludedResponses?)
|
||||||
|
{
|
||||||
|
self.name = response.attributes.title
|
||||||
|
self.identifier = response.id
|
||||||
|
|
||||||
|
guard let included, let benefitIDs = response.relationships?.benefits?.data.map(\.id) else { return }
|
||||||
|
|
||||||
|
let benefits = benefitIDs.compactMap { included.benefits[$0] }.map(Benefit.init(response:))
|
||||||
|
self.benefits = benefits
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
49
AltStoreCore/Patreon/UserAccount.swift
Normal file
49
AltStoreCore/Patreon/UserAccount.swift
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// Account.swift
|
||||||
|
// AltStoreCore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 11/3/23.
|
||||||
|
// Copyright © 2023 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension PatreonAPI
|
||||||
|
{
|
||||||
|
typealias UserAccountResponse = DataResponse<UserAccountAttributes, AnyRelationships>
|
||||||
|
|
||||||
|
struct UserAccountAttributes: Decodable
|
||||||
|
{
|
||||||
|
var first_name: String?
|
||||||
|
var full_name: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PatreonAPI
|
||||||
|
{
|
||||||
|
public struct UserAccount
|
||||||
|
{
|
||||||
|
var name: String
|
||||||
|
var firstName: String?
|
||||||
|
var identifier: String
|
||||||
|
|
||||||
|
// Relationships
|
||||||
|
var pledges: [Patron]?
|
||||||
|
|
||||||
|
init(response: UserAccountResponse, including included: IncludedResponses?)
|
||||||
|
{
|
||||||
|
self.identifier = response.id
|
||||||
|
self.name = response.attributes.full_name
|
||||||
|
self.firstName = response.attributes.first_name
|
||||||
|
|
||||||
|
guard let included else { return }
|
||||||
|
|
||||||
|
let patrons = included.patrons.values.compactMap { response -> Patron? in
|
||||||
|
let patron = Patron(response: response, including: included)
|
||||||
|
return patron
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pledges = patrons
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
AltStoreCore/Types/ALTPatreonBenefitID.h
Normal file
13
AltStoreCore/Types/ALTPatreonBenefitID.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// ALTPatreonBenefitType.h
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/27/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
typedef NSString *ALTPatreonBenefitID NS_TYPED_EXTENSIBLE_ENUM;
|
||||||
|
extern ALTPatreonBenefitID const ALTPatreonBenefitIDBetaAccess;
|
||||||
|
extern ALTPatreonBenefitID const ALTPatreonBenefitIDCredits;
|
||||||
12
AltStoreCore/Types/ALTPatreonBenefitID.m
Normal file
12
AltStoreCore/Types/ALTPatreonBenefitID.m
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// ALTPatreonBenefitType.m
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 8/27/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "ALTPatreonBenefitID.h"
|
||||||
|
|
||||||
|
ALTPatreonBenefitID const ALTPatreonBenefitIDBetaAccess = @"1186336";
|
||||||
|
ALTPatreonBenefitID const ALTPatreonBenefitIDCredits = @"1186340";
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTPatreonBenefitType.h
|
|
||||||
// AltStore
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 8/27/19.
|
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
typedef NSString *ALTPatreonBenefitType NS_TYPED_EXTENSIBLE_ENUM;
|
|
||||||
extern ALTPatreonBenefitType const ALTPatreonBenefitTypeBetaAccess;
|
|
||||||
extern ALTPatreonBenefitType const ALTPatreonBenefitTypeCredits;
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//
|
|
||||||
// ALTPatreonBenefitType.m
|
|
||||||
// AltStore
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 8/27/19.
|
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "ALTPatreonBenefitType.h"
|
|
||||||
|
|
||||||
ALTPatreonBenefitType const ALTPatreonBenefitTypeBetaAccess = @"1186336";
|
|
||||||
ALTPatreonBenefitType const ALTPatreonBenefitTypeCredits = @"1186340";
|
|
||||||
Reference in New Issue
Block a user