├── icon.png ├── screenshot ├── 01.png ├── 02.png ├── 03.png ├── 04.png ├── 05.png ├── 06.png ├── 07.png ├── 08.png ├── 09.png └── 10.png ├── AppleParty ├── Resources │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── RootView │ │ │ ├── Contents.json │ │ │ ├── Apps.imageset │ │ │ │ ├── Apps@2x.png │ │ │ │ └── Contents.json │ │ │ ├── QRcode.imageset │ │ │ │ ├── QRcode@2x.png │ │ │ │ └── Contents.json │ │ │ ├── IPAUpload.imageset │ │ │ │ ├── IPAUpload@2x.png │ │ │ │ └── Contents.json │ │ │ ├── SendEmail.imageset │ │ │ │ ├── SendEmail@2x.png │ │ │ │ └── Contents.json │ │ │ ├── AppAnalytics.imageset │ │ │ │ ├── AppAnalytics@2x.png │ │ │ │ └── Contents.json │ │ │ ├── VerifyReceipt.imageset │ │ │ │ ├── VerifyReceipt@2x.png │ │ │ │ └── Contents.json │ │ │ ├── PlaceholderIcon.imageset │ │ │ │ ├── PlaceholderIcon@2x.png │ │ │ │ ├── PlaceholderIcon_white@2x.png │ │ │ │ └── Contents.json │ │ │ └── FinancialReports.imageset │ │ │ │ ├── FinancialReports@2x.png │ │ │ │ └── Contents.json │ │ ├── 37MobileGames │ │ │ ├── Contents.json │ │ │ ├── 37M-logo.imageset │ │ │ │ ├── 37M-logo.png │ │ │ │ └── Contents.json │ │ │ ├── 37M-slogan.imageset │ │ │ │ ├── 37M-slogan.png │ │ │ │ └── Contents.json │ │ │ └── 37iOSTeam-Round.imageset │ │ │ │ ├── 37iOSTeam-Round.png │ │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── icon-16.png │ │ │ ├── icon-32.png │ │ │ ├── icon-64.png │ │ │ ├── icon-1024.png │ │ │ ├── icon-128.png │ │ │ ├── icon-256.png │ │ │ ├── icon-512.png │ │ │ └── Contents.json │ │ ├── ApplePartyIcon.imageset │ │ │ ├── icon-256.png │ │ │ ├── icon-512.png │ │ │ └── Contents.json │ │ └── AccentColor.colorset │ │ │ └── Contents.json │ ├── InAppPurchase │ │ └── example.xlsx │ └── Transporter │ │ ├── ipa_metadata.xml │ │ ├── shot_metadata.xml │ │ └── iap_metadata.xml ├── AppListView │ ├── zh-Hans.lproj │ │ └── AppList.strings │ ├── ScreenShotsView │ │ ├── ScreenShotHelpPopoverVC.swift │ │ └── ScreenShotUploadCell.swift │ ├── InAppPurchseView │ │ ├── APInappPurchseCell.xib │ │ ├── OutputExcelVC.swift │ │ ├── DragView.swift │ │ ├── IAPUploadImageVC.swift │ │ └── APInAppPurchseCell.swift │ ├── APAppListCell.swift │ ├── APAppListVC.swift │ ├── APAppListAdapter.swift │ ├── Base.lproj │ │ └── AppList.storyboard │ ├── APAppListModel.swift │ └── APAppListCell.xib ├── en.lproj │ ├── InfoPlist.strings │ └── Localizable.strings ├── zh-Hans.lproj │ ├── Localizable.strings │ └── InfoPlist.strings ├── AppleParty.entitlements ├── AppleParty-Bridging-Header.h ├── Vendors │ ├── QrcodeUtil.h │ ├── ITMS │ │ ├── XMLManager.swift │ │ └── GDataXMLNode.h │ └── QrcodeUtil.m ├── RootView │ ├── APRootCollectionModel.swift │ ├── APRootCollectionCell.swift │ ├── APSwichAccountPopover.swift │ ├── APRootVC.swift │ ├── APRootCollectionAdapter.swift │ ├── APRootCollectionCell.xib │ └── APRootWC.swift ├── Shared │ ├── Info │ │ ├── APConstants.swift │ │ ├── UserCenter.swift │ │ └── InfoCenter.swift │ ├── UI │ │ ├── UIExtension.swift │ │ ├── APSPasswordEditVC.swift │ │ ├── APCollectionView.swift │ │ ├── APASCKeysEditVC.swift │ │ ├── APDebugVC.swift │ │ ├── APSPasswordSettingVC.swift │ │ └── APASCKeysSettingVC.swift │ └── Utils │ │ ├── APHUD.swift │ │ ├── ARLogs.swift │ │ ├── EmailUtils.swift │ │ └── APUtil.swift ├── Info.plist ├── LoginView │ ├── PhoneNumbers.swift │ ├── AppleWebLogin │ │ └── AppleWebLoginCore.swift │ ├── APWebLoginVC.swift │ └── APLogin2FAVC.swift ├── SparkleUpdate │ ├── update.xml │ └── AppleParty-release.html ├── AppSettingView │ └── APSettingVC.swift ├── AppDelegate.swift ├── EmailToolView │ └── EmailSettingVC.swift ├── VerifyReceipt │ └── APVerifyReceiptVC.swift ├── IPAUpload │ └── APIPAUploadVC.swift └── QRcodeView │ └── APQRcodeVC.swift ├── AppleParty.xcodeproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── AppleParty.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── Podfile ├── ApplePartyUITests ├── ApplePartyUITestsLaunchTests.swift └── ApplePartyUITests.swift ├── ApplePartyTests └── ApplePartyTests.swift ├── Podfile.lock ├── .gitignore └── README.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/icon.png -------------------------------------------------------------------------------- /screenshot/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/screenshot/01.png -------------------------------------------------------------------------------- /screenshot/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/screenshot/02.png -------------------------------------------------------------------------------- /screenshot/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/screenshot/03.png -------------------------------------------------------------------------------- /screenshot/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/screenshot/04.png -------------------------------------------------------------------------------- /screenshot/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/screenshot/05.png -------------------------------------------------------------------------------- /screenshot/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/screenshot/06.png -------------------------------------------------------------------------------- /screenshot/07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/screenshot/07.png -------------------------------------------------------------------------------- /screenshot/08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/screenshot/08.png -------------------------------------------------------------------------------- /screenshot/09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/screenshot/09.png -------------------------------------------------------------------------------- /screenshot/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/screenshot/10.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/37MobileGames/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppleParty/Resources/InAppPurchase/example.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/InAppPurchase/example.xlsx -------------------------------------------------------------------------------- /AppleParty/AppListView/zh-Hans.lproj/AppList.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSWindow"; title = "My Apps"; ObjectID = "mcp-fI-0bQ"; */ 3 | "mcp-fI-0bQ.title" = "我的 App"; 4 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-16.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-32.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-64.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-128.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-256.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/icon-512.png -------------------------------------------------------------------------------- /AppleParty/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | AppleParty 4 | 5 | Created by HTC on 2022/3/10. 6 | Copyright © 2022 37 Mobile Games. All rights reserved. 7 | */ 8 | -------------------------------------------------------------------------------- /AppleParty/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | AppleParty 4 | 5 | Created by HTC on 2022/3/10. 6 | Copyright © 2022 37 Mobile Games. All rights reserved. 7 | */ 8 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/Apps.imageset/Apps@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/RootView/Apps.imageset/Apps@2x.png -------------------------------------------------------------------------------- /AppleParty/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | AppleParty 4 | 5 | Created by HTC on 2022/3/10. 6 | Copyright © 2022 37 Mobile Games. All rights reserved. 7 | */ 8 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/ApplePartyIcon.imageset/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/ApplePartyIcon.imageset/icon-256.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/ApplePartyIcon.imageset/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/ApplePartyIcon.imageset/icon-512.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/QRcode.imageset/QRcode@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/RootView/QRcode.imageset/QRcode@2x.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/37MobileGames/37M-logo.imageset/37M-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/37MobileGames/37M-logo.imageset/37M-logo.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/IPAUpload.imageset/IPAUpload@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/RootView/IPAUpload.imageset/IPAUpload@2x.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/SendEmail.imageset/SendEmail@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/RootView/SendEmail.imageset/SendEmail@2x.png -------------------------------------------------------------------------------- /AppleParty.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/37MobileGames/37M-slogan.imageset/37M-slogan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/37MobileGames/37M-slogan.imageset/37M-slogan.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/AppAnalytics.imageset/AppAnalytics@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/RootView/AppAnalytics.imageset/AppAnalytics@2x.png -------------------------------------------------------------------------------- /AppleParty/AppleParty.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/VerifyReceipt.imageset/VerifyReceipt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/RootView/VerifyReceipt.imageset/VerifyReceipt@2x.png -------------------------------------------------------------------------------- /AppleParty/AppleParty-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "GDataXMLNode.h" 6 | #import "QrcodeUtil.h" 7 | #import "MBProgressHUD.h" 8 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/PlaceholderIcon.imageset/PlaceholderIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/RootView/PlaceholderIcon.imageset/PlaceholderIcon@2x.png -------------------------------------------------------------------------------- /AppleParty/zh-Hans.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | AppleParty 4 | 5 | Created by HTC on 2022/3/10. 6 | Copyright © 2022 37 Mobile Games. All rights reserved. 7 | */ 8 | 9 | 10 | "CFBundleName" = "苹果派"; 11 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/37MobileGames/37iOSTeam-Round.imageset/37iOSTeam-Round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/37MobileGames/37iOSTeam-Round.imageset/37iOSTeam-Round.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/FinancialReports.imageset/FinancialReports@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/RootView/FinancialReports.imageset/FinancialReports@2x.png -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/PlaceholderIcon.imageset/PlaceholderIcon_white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/AppleParty/HEAD/AppleParty/Resources/Assets.xcassets/RootView/PlaceholderIcon.imageset/PlaceholderIcon_white@2x.png -------------------------------------------------------------------------------- /AppleParty.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AppleParty.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AppleParty.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AppleParty/Vendors/QrcodeUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // QrcodeUtil.h 3 | // 4 | // Created by HTC on 2019/11/25. 5 | // 6 | 7 | #import 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | NSDictionary* scanQRCodeOnScreen(); 13 | 14 | NSImage* createQRImage(NSString *string, NSSize size); 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Tell us how we can improve AppleParty** 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | 14 | **What would you like to see? How would you like it to work?** 15 | -------------------------------------------------------------------------------- /AppleParty/RootView/APRootCollectionModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APRootCollectionModel.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/14. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | 12 | struct APRootCollectionModel { 13 | let name: String 14 | let icon: String 15 | let handler: (() -> Void)? 16 | 17 | } 18 | -------------------------------------------------------------------------------- /AppleParty/AppListView/ScreenShotsView/ScreenShotHelpPopoverVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenShotHelpPopoverVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/2/28. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ScreenShotHelpPopoverVC: NSViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do view setup here. 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/Apps.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "Apps@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/QRcode.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "QRcode@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/37MobileGames/37M-logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "37M-logo.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/37MobileGames/37M-slogan.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "37M-slogan.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/IPAUpload.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "IPAUpload@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/SendEmail.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "SendEmail@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/AppAnalytics.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "AppAnalytics@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/VerifyReceipt.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "VerifyReceipt@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/37MobileGames/37iOSTeam-Round.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "37iOSTeam-Round.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/FinancialReports.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "FinancialReports@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/ApplePartyIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "icon-256.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "icon-512.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AppleParty/Resources/Transporter/ipa_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {file_size} 7 | {file_name} 8 | {file_md5} 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Version** 23 | - OS: 24 | - AppleParty: 25 | -------------------------------------------------------------------------------- /AppleParty/Shared/Info/APConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APConstants.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/4/7. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let kApplePartyGitHub = "https://github.com/37iOS/AppleParty" 12 | let kApplePartyWiKi = "https://github.com/37iOS/AppleParty/wiki" 13 | let kApplePartyNewIssues = "https://github.com/37iOS/AppleParty/issues/new/choose" 14 | let k37MobileGamesSite = "https://www.37.com.cn" 15 | let k37iOSTeamJueJinSite = "https://juejin.cn/user/1002387318511214" 16 | -------------------------------------------------------------------------------- /AppleParty/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | SUFeedURL 11 | https://raw.githubusercontent.com/37iOS/AppleParty/main/AppleParty/SparkleUpdate/update.xml 12 | SUPublicEDKey 13 | ItVUtr4L9w9VfMGlzg7+cIcvSkruiygDcarlq8PTF7I= 14 | 15 | 16 | -------------------------------------------------------------------------------- /AppleParty/RootView/APRootCollectionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APRootCollectionCell.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/14. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APRootCollectionCell: NSCollectionViewItem { 12 | 13 | @IBOutlet weak var imgView: NSImageView! 14 | @IBOutlet weak var nameView: NSTextField! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | } 19 | 20 | func configure(name: String, icon: String) { 21 | nameView.stringValue = name 22 | imgView?.image = NSImage(named: icon) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /AppleParty/LoginView/PhoneNumbers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhoneNumbers.swift 3 | // AppleParty 4 | // 5 | // Created by 易承 on 2021/6/2. 6 | // 7 | 8 | import Foundation 9 | 10 | struct PNumber { 11 | var num: String 12 | var id: Int 13 | } 14 | 15 | // MARK: - 双重绑定手机号码 16 | struct PhoneNumbers { 17 | var numbers: [PNumber] 18 | 19 | init(body: [String: Any]) { 20 | numbers = [PNumber]() 21 | let trustedPhoneNumbers = dictionaryArray(body["trustedPhoneNumbers"]) 22 | for phone in trustedPhoneNumbers { 23 | numbers.append(PNumber(num: string(from: phone["numberWithDialCode"], defaultValue: "未知手机号"), 24 | id: int(from: phone["id"]) ?? 0 )) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AppleParty/AppListView/InAppPurchseView/APInappPurchseCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'AppleParty' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for AppleParty 9 | pod 'Alamofire', '~> 5.9.1' 10 | pod 'SnapKit', '~> 5.0' 11 | pod 'Sparkle', '~> 2.6.3' 12 | pod 'Kanna', '~> 5.2' 13 | pod 'SWXMLHash', '~> 5.0' 14 | pod 'CoreXLSX', '~> 0.14' 15 | pod 'ExpandingDatePicker', '~> 1.0' 16 | pod 'KeychainAccess', '~> 4.2' 17 | 18 | target 'ApplePartyTests' do 19 | inherit! :search_paths 20 | # Pods for testing 21 | end 22 | 23 | target 'ApplePartyUITests' do 24 | # Pods for testing 25 | end 26 | 27 | end 28 | 29 | post_install do |installer| 30 | installer.pods_project.targets.each do |target| 31 | target.build_configurations.each do |config| 32 | config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '11.0' 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /ApplePartyUITests/ApplePartyUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplePartyUITestsLaunchTests.swift 3 | // ApplePartyUITests 4 | // 5 | // Created by HTC on 2022/3/10. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class ApplePartyUITestsLaunchTests: XCTestCase { 12 | 13 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 14 | true 15 | } 16 | 17 | override func setUpWithError() throws { 18 | continueAfterFailure = false 19 | } 20 | 21 | func testLaunch() throws { 22 | let app = XCUIApplication() 23 | app.launch() 24 | 25 | // Insert steps here to perform after app launch but before taking a screenshot, 26 | // such as logging into a test account or navigating somewhere in the app 27 | 28 | let attachment = XCTAttachment(screenshot: app.screenshot()) 29 | attachment.name = "Launch Screen" 30 | attachment.lifetime = .keepAlways 31 | add(attachment) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/RootView/PlaceholderIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "idiom" : "universal", 15 | "scale" : "1x" 16 | }, 17 | { 18 | "filename" : "PlaceholderIcon@2x.png", 19 | "idiom" : "universal", 20 | "scale" : "2x" 21 | }, 22 | { 23 | "appearances" : [ 24 | { 25 | "appearance" : "luminosity", 26 | "value" : "dark" 27 | } 28 | ], 29 | "filename" : "PlaceholderIcon_white@2x.png", 30 | "idiom" : "universal", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "universal", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "appearances" : [ 39 | { 40 | "appearance" : "luminosity", 41 | "value" : "dark" 42 | } 43 | ], 44 | "idiom" : "universal", 45 | "scale" : "3x" 46 | } 47 | ], 48 | "info" : { 49 | "author" : "xcode", 50 | "version" : 1 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /AppleParty/AppListView/APAppListCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APAppListCell.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/17. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APAppListCell: NSCollectionViewItem { 12 | 13 | public var purchseHandle: ((_ app: App) -> Void)? 14 | public var screenshotHandle: ((_ app: App) -> Void)? 15 | 16 | @IBOutlet weak var imgView: NSImageView! 17 | @IBOutlet weak var nameView: NSTextField! 18 | 19 | private var app: App? 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | nameView.maximumNumberOfLines = 2 24 | imgView.wantsLayer = true 25 | imgView.layer?.cornerRadius = 22 26 | imgView.layer?.masksToBounds = true 27 | } 28 | 29 | @IBAction func clickedPurchseItem(_ sender: Any) { 30 | purchseHandle?(app!) 31 | } 32 | 33 | @IBAction func clickedScreenshotItem(_ sender: Any) { 34 | screenshotHandle?(app!) 35 | } 36 | 37 | func configure(app: App) { 38 | self.app = app 39 | nameView.stringValue = app.appName 40 | imgView?.showWebImage(app.iconUrl) 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /AppleParty/SparkleUpdate/update.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AppleParty for macOS Changelog 5 | Most recent changes with links to updates. 6 | zh_CN 7 | 8 | Version 3.8.0 9 | https://37mobileteam.github.io/Sparkle/AppleParty-release.html 10 | 2025-09-29 11 | 19 | 11.0 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /AppleParty/Shared/UI/UIExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIExtension.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/12. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | import Foundation 11 | 12 | 13 | extension NSAlert { 14 | 15 | static func show(_ content: String, title: String = "提示") { 16 | let alert = NSAlert() 17 | alert.messageText = title 18 | alert.informativeText = content 19 | alert.runModal() 20 | } 21 | } 22 | 23 | 24 | extension NSImageView { 25 | func showWebImage(_ url: URL) { 26 | URLSession.shared.dataTask(with: url) { data, response, error in 27 | guard 28 | let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200, 29 | let mimeType = response?.mimeType, mimeType.hasPrefix("image"), 30 | let data = data, error == nil, 31 | let image = NSImage(data: data) 32 | else { return } 33 | DispatchQueue.main.async() { [weak self] in 34 | self?.image = image 35 | } 36 | }.resume() 37 | } 38 | 39 | func showWebImage(_ link: String) { 40 | guard let url = URL(string: link) else { return } 41 | showWebImage(url) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ApplePartyTests/ApplePartyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplePartyTests.swift 3 | // ApplePartyTests 4 | // 5 | // Created by HTC on 2022/3/10. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import AppleParty 11 | 12 | class ApplePartyTests: XCTestCase { 13 | 14 | override func setUpWithError() throws { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDownWithError() throws { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() throws { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | // Any test you write for XCTest can be annotated as throws and async. 26 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 27 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 28 | } 29 | 30 | func testPerformanceExample() throws { 31 | // This is an example of a performance test case. 32 | self.measure { 33 | // Put the code you want to measure the time of here. 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /AppleParty/AppSettingView/APSettingVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/25. 6 | // Copyright © 2021 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APSettingVC: NSViewController { 12 | 13 | var isLoginViewShow: Bool { 14 | get { return false } 15 | set { 16 | sPasswordBtn.isHidden = newValue 17 | clearCacheBtn.isHidden = !newValue 18 | } 19 | } 20 | 21 | @IBOutlet weak var trusDeviceBtn: NSButton! 22 | @IBOutlet weak var sPasswordBtn: NSButton! 23 | @IBOutlet weak var clearCacheBtn: NSButton! 24 | 25 | @IBAction func clickedTrusDeviceBtn(_ sender: NSButton) { 26 | InfoCenter.shared.trusDevice = sender.state == .on ? true : false 27 | } 28 | 29 | @IBAction func clickedSPasswordBtn(_ sender: Any) { 30 | let vc = APSPasswordSettingVC() 31 | presentAsSheet(vc) 32 | } 33 | 34 | 35 | @IBAction func clickedClearCacheBtn(_ sender: NSButton) { 36 | // 清掉缓存 37 | HTTPCookieStorage.shared.cookies?.forEach(HTTPCookieStorage.shared.deleteCookie) 38 | InfoCenter.shared.cookies = [] 39 | APHUD.hide(message: "清掉缓存成功", view: self.view) 40 | } 41 | 42 | 43 | override func viewDidLoad() { 44 | super.viewDidLoad() 45 | title = "App设置" 46 | trusDeviceBtn.state = InfoCenter.shared.trusDevice ? .on : .off 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /AppleParty/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "filename" : "icon-16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "idiom" : "mac", 11 | "size" : "16x16", 12 | "filename" : "icon-32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "idiom" : "mac", 17 | "size" : "32x32", 18 | "filename" : "icon-32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "idiom" : "mac", 23 | "size" : "32x32", 24 | "filename" : "icon-64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "idiom" : "mac", 29 | "size" : "128x128", 30 | "filename" : "icon-128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "128x128", 36 | "filename" : "icon-256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "idiom" : "mac", 41 | "size" : "256x256", 42 | "filename" : "icon-256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "idiom" : "mac", 47 | "size" : "256x256", 48 | "filename" : "icon-512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "idiom" : "mac", 53 | "size" : "512x512", 54 | "filename" : "icon-512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "idiom" : "mac", 59 | "size" : "512x512", 60 | "filename" : "icon-1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /AppleParty/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/10. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Sparkle 11 | 12 | @main 13 | class AppDelegate: NSObject, NSApplicationDelegate { 14 | 15 | var mainWindow: NSWindow? 16 | @IBOutlet weak var updaterController: SPUStandardUpdaterController! 17 | 18 | func applicationDidFinishLaunching(_ aNotification: Notification) { 19 | // 后台检查更新 20 | updaterController.updater.checkForUpdatesInBackground() 21 | } 22 | 23 | func applicationWillTerminate(_ aNotification: Notification) { 24 | // Insert code here to tear down your application 25 | } 26 | 27 | func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 28 | return true 29 | } 30 | 31 | /// 当关闭最后一个窗口时,退出app 32 | /// - Parameter sender: 33 | /// - Returns: true-窗口程序两者都关闭,false-只关闭窗口 34 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 35 | true 36 | } 37 | 38 | /// 应用窗口重新打开时 39 | /// 40 | /// - Parameters: 41 | /// - sender: 42 | /// - flag: 43 | /// - Returns: 44 | func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { 45 | return true 46 | } 47 | 48 | @IBAction func showHelp(_ sender: Any) { 49 | let url = URL(string: kApplePartyWiKi) 50 | NSWorkspace.shared.open(url!) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.9.1) 3 | - CoreXLSX (0.14.1): 4 | - XMLCoder (~> 0.11.1) 5 | - ZIPFoundation (~> 0.9.11) 6 | - ExpandingDatePicker (1.0.2) 7 | - Kanna (5.2.7) 8 | - KeychainAccess (4.2.2) 9 | - SnapKit (5.0.1) 10 | - Sparkle (2.6.4) 11 | - SWXMLHash (5.0.1) 12 | - XMLCoder (0.11.1) 13 | - ZIPFoundation (0.9.13) 14 | 15 | DEPENDENCIES: 16 | - Alamofire (~> 5.9.1) 17 | - CoreXLSX (~> 0.14) 18 | - ExpandingDatePicker (~> 1.0) 19 | - Kanna (~> 5.2) 20 | - KeychainAccess (~> 4.2) 21 | - SnapKit (~> 5.0) 22 | - Sparkle (~> 2.6.3) 23 | - SWXMLHash (~> 5.0) 24 | 25 | SPEC REPOS: 26 | trunk: 27 | - Alamofire 28 | - CoreXLSX 29 | - ExpandingDatePicker 30 | - Kanna 31 | - KeychainAccess 32 | - SnapKit 33 | - Sparkle 34 | - SWXMLHash 35 | - XMLCoder 36 | - ZIPFoundation 37 | 38 | SPEC CHECKSUMS: 39 | Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c 40 | CoreXLSX: f9a7a70b509150ceb675b05877b7ac041a587030 41 | ExpandingDatePicker: 87635f25b5b6613814db39621e704815a11cf5c1 42 | Kanna: 01cfbddc127f5ff0963692f285fcbc8a9d62d234 43 | KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51 44 | SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb 45 | Sparkle: 5f8960a7a119aa7d45dacc0d5837017170bc5675 46 | SWXMLHash: 9cc0c2e4807926c74377724aa8722ee5707a0485 47 | XMLCoder: c4638336674de7ba279cd23f2005817d69f538e0 48 | ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37 49 | 50 | PODFILE CHECKSUM: 45121375cceeff895db3e4fc13259f669c881a4a 51 | 52 | COCOAPODS: 1.16.2 53 | -------------------------------------------------------------------------------- /ApplePartyUITests/ApplePartyUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplePartyUITests.swift 3 | // ApplePartyUITests 4 | // 5 | // Created by HTC on 2022/3/10. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class ApplePartyUITests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDownWithError() throws { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testExample() throws { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | 31 | // Use recording to get started writing UI tests. 32 | // Use XCTAssert and related functions to verify your tests produce the correct results. 33 | } 34 | 35 | func testLaunchPerformance() throws { 36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 37 | // This measures how long it takes to launch your application. 38 | measure(metrics: [XCTApplicationLaunchMetric()]) { 39 | XCUIApplication().launch() 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AppleParty/Shared/Utils/APHUD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APHUD.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/18. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | let APHUD = HUD.shared 12 | 13 | class HUD: NSObject { 14 | 15 | static let shared = HUD() 16 | 17 | private var loadHud: MBProgressHUD? 18 | private var textHud: MBProgressHUD? 19 | 20 | func showLoading(_ view: NSView = currentView()) { 21 | if loadHud != nil { 22 | loadHud?.hide(true) 23 | } 24 | guard let loadHud = MBProgressHUD(view: view) else { 25 | return 26 | } 27 | self.loadHud = loadHud 28 | view.addSubview(loadHud) 29 | loadHud.show(true) 30 | } 31 | 32 | func hideLoading() { 33 | loadHud?.hide(true) 34 | } 35 | 36 | func show(message: String, view: NSView = currentView()) { 37 | if textHud != nil { 38 | textHud?.hide(true) 39 | } 40 | guard let textHud = MBProgressHUD(view: view) else { 41 | return 42 | } 43 | self.textHud = textHud 44 | textHud.labelText = message 45 | view.addSubview(textHud) 46 | textHud.show(true) 47 | } 48 | 49 | func hide() { 50 | textHud?.hide(true) 51 | } 52 | 53 | func hide(message: String, view: NSView = currentView(), delayTime: TimeInterval = 3) { 54 | guard let hud = MBProgressHUD(view: view) else { 55 | return 56 | } 57 | hud.mode = MBProgressHUDModeText 58 | hud.labelText = message 59 | hud.removeFromSuperViewOnHide = true 60 | view.addSubview(hud) 61 | hud.show(true) 62 | hud.hide(true, afterDelay: delayTime) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /AppleParty/EmailToolView/EmailSettingVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmailSettingVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/29. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | 12 | struct EamilConfigs { 13 | var name: String = "" 14 | var addr: String = "" 15 | var pwd: String = "" 16 | var smtp: String = "" 17 | } 18 | 19 | var eamilConfigs: EamilConfigs? { 20 | get { 21 | let value = (try? APUtil.keychain.getString("APEmailSetting_Key")) ?? "" 22 | let arr = value.components(separatedBy: "|") 23 | guard arr.count == 4 else { 24 | return nil 25 | } 26 | return EamilConfigs(name: arr[0], addr: arr[1], pwd: arr[2], smtp: arr[3]) 27 | } 28 | set { 29 | guard let newValue = newValue else { return } 30 | let value = "\(newValue.name)|\(newValue.addr)|\(newValue.pwd)|\(newValue.smtp)" 31 | try? APUtil.keychain.set(value, key: "APEmailSetting_Key") 32 | } 33 | } 34 | 35 | class EmailSettingVC: NSViewController { 36 | 37 | public var closeHandle: (() -> Void)? 38 | 39 | @IBOutlet weak var emailNameView: NSTextField! 40 | @IBOutlet weak var emailAddrView: NSTextField! 41 | @IBOutlet weak var emailPwdView: NSTextField! 42 | @IBOutlet weak var emailSMTPView: NSTextField! 43 | 44 | 45 | 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | // Do view setup here. 49 | } 50 | 51 | @IBAction func clickedCancelBtn(_ sender: Any) { 52 | closeHandle?() 53 | } 54 | 55 | @IBAction func clickedSubmitBtn(_ sender: Any) { 56 | 57 | eamilConfigs = EamilConfigs(name: emailNameView.stringValue, addr: emailAddrView.stringValue, pwd: emailPwdView.stringValue, smtp: emailSMTPView.stringValue) 58 | closeHandle?() 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /AppleParty/Shared/UI/APSPasswordEditVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APSPasswordEditVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/5/18. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APSPasswordEditVC: NSViewController { 12 | 13 | @IBOutlet weak var titleLbl: NSTextField! 14 | @IBOutlet var accountTextView: NSTextField! 15 | @IBOutlet var passwordTextView: NSTextField! 16 | @IBOutlet weak var usePasswordBtn: NSButton! 17 | 18 | public var titleString: String? 19 | public var spassword: SPassword? 20 | public var updateCompletion: ((_ model: SPassword) -> Void)? 21 | 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | if let text = titleString { 27 | titleLbl.stringValue = text 28 | } 29 | 30 | if let model = spassword { 31 | accountTextView.stringValue = model.account 32 | passwordTextView.stringValue = model.password 33 | usePasswordBtn.state = model.isused ? .on : .off 34 | } else { 35 | // 新建时,默认读取当前账号的邮件名 36 | accountTextView.stringValue = UserCenter.shared.loginedUser.appleid 37 | } 38 | } 39 | 40 | @IBAction func clickedCancelBtn(_ sender: Any) { 41 | dismiss(self) 42 | } 43 | 44 | @IBAction func clickedSaveBtn(_ sender: Any) { 45 | let account = accountTextView.stringValue.trim() 46 | let password = passwordTextView.stringValue.trim() 47 | 48 | guard account.isNotEmpty, password.isNotEmpty else { 49 | APHUD.hide(message: "账号邮箱和专用密码不能为空!", view: view, delayTime: 1) 50 | return 51 | } 52 | 53 | if let block = updateCompletion { 54 | block(SPassword(account: account, password: password, isused: usePasswordBtn.state == .on)) 55 | } 56 | dismiss(self) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /AppleParty/Resources/Transporter/shot_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {provider} 4 | {provider} 5 | 6 | {vendor_id} 7 | 8 | 9 | 10 | 11 | 12 | {title} 13 | 14 | 15 | 16 | {video_size} 17 | {video_name} 18 | {video_md5} 19 | 20 | {00:00:05:00} 21 | 22 | 23 | 24 | 25 | {image_size} 26 | {image_name} 27 | {image_md5} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /AppleParty/AppListView/APAppListVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APAppListVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/17. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APAppListVC: NSViewController { 12 | 13 | fileprivate var adapter: APAppListAdapter? 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | configureCollectionView() 18 | fetchAppList() 19 | } 20 | 21 | /// 配置显示的功能列表 22 | func configureCollectionView() { 23 | let colview = APCollectionView() 24 | colview.configure(superView: view) 25 | adapter = APAppListAdapter(collectionView: colview.collectionView) 26 | adapter?.purchseHandle = { [weak self] app in 27 | let sb = NSStoryboard(name: "APInAppPurchseVC", bundle: nil) 28 | let wc = sb.instantiateController(withIdentifier: "APInAppPurchseVC") as! NSWindowController 29 | let vc = wc.contentViewController as! APInAppPurchseVC 30 | vc.currentApp = app 31 | wc.showWindow(self) 32 | } 33 | adapter?.screenshotHandle = { [weak self] app in 34 | let sb = NSStoryboard(name: "ScreenShotUpload", bundle: nil) 35 | let wc = sb.instantiateController(withIdentifier: "ScreenShotUploadVC") as! NSWindowController 36 | let vc = wc.contentViewController as! ScreenShotUploadVC 37 | vc.currentApp = app 38 | wc.showWindow(self) 39 | } 40 | } 41 | 42 | } 43 | 44 | 45 | // MARK: - 网络请求 46 | extension APAppListVC { 47 | 48 | func fetchAppList() { 49 | APClient.appList(status: .filter(nil)).request(showLoading: true, inView: self.view) { [weak self] result, response, error in 50 | guard let err = error else { 51 | let gamelist = AppList(body: result) 52 | self?.adapter?.set(items: gamelist.games) 53 | return 54 | } 55 | APHUD.hide(message: err.localizedDescription) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /AppleParty/AppListView/InAppPurchseView/OutputExcelVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutputExcelVC.swift 3 | // AppleParty 4 | // 5 | // Created by 易承 on 2020/12/23. 6 | // 7 | 8 | import Cocoa 9 | 10 | class OutputExcelVC: NSViewController, NSTextViewDelegate { 11 | 12 | @IBOutlet var inputText: NSTextView! 13 | @IBOutlet weak var outputView: NSScrollView! 14 | @IBOutlet var outputText: NSTextView! 15 | 16 | @IBOutlet weak var inputCount: NSTextField! 17 | @IBOutlet weak var outputCount: NSTextField! 18 | 19 | var inputs = [String]() 20 | var outputs = [String]() 21 | 22 | var iapList: [IAPList.IAP] = [] 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | inputText.delegate = self 27 | } 28 | 29 | @IBAction func close(_ sender: Any) { 30 | dismiss(self) 31 | } 32 | 33 | @IBAction func commit(_ sender: Any) { 34 | outputs.removeAll() 35 | for i in 0.. 0 } 54 | inputCount.stringValue = String(inputs.count)+"/100" 55 | } 56 | 57 | func checkCount() -> Bool { 58 | guard inputs.count == outputs.count else { 59 | inputCount.textColor = NSColor.red 60 | outputCount.textColor = NSColor.red 61 | return false 62 | } 63 | inputCount.textColor = NSColor.lightGray 64 | outputCount.textColor = NSColor.lightGray 65 | return true 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /AppleParty/Shared/Utils/ARLogs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APLogs.swift 3 | // AppleParty 4 | // 5 | // Created by iHTC on 20210930. 6 | // Copyright © 2021 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | class APLogs { 13 | static let shared = APLogs() 14 | 15 | func add(_ log: String, printlog: Bool = false, retry: Int = 3) { 16 | let dateFormatter : DateFormatter = DateFormatter() 17 | dateFormatter.dateFormat = "[MM-dd HH:mm:ss] " 18 | let currentDateString = dateFormatter.string(from: Date()) 19 | let out = currentDateString + log 20 | 21 | if printlog { 22 | debugPrint(out) 23 | } 24 | 25 | dateFormatter.dateFormat = "yyyyMMdd_HH00" 26 | let dataPath = dateFormatter.string(from: Date()) 27 | 28 | var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] 29 | documentsURL.appendPathComponent("AppleParty") 30 | documentsURL.appendPathComponent("Logs") 31 | documentsURL.appendPathComponent("\(dataPath)_log.txt") 32 | createFileDirectory(url: documentsURL) 33 | 34 | let path = documentsURL 35 | do { 36 | try out.appendLine(to: path) 37 | } catch { 38 | if retry > 0 { 39 | print("‼️ retry save logs file~ error:\(error)") 40 | add(log, printlog: printlog, retry: retry - 1) 41 | } 42 | } 43 | } 44 | 45 | 46 | func createFileDirectory(url: URL) { 47 | let fm = FileManager.default 48 | let directory = url.deletingLastPathComponent() 49 | var isDirectory: ObjCBool = false 50 | // 保证目录存在,不存在就创建目录 51 | if !(fm.fileExists(atPath: directory.path, isDirectory: &isDirectory) && isDirectory.boolValue) { 52 | do { 53 | try fm.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) 54 | } catch { 55 | print("‼️ create Directory file error:\(error), \(url.path)") 56 | } 57 | } 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /AppleParty/RootView/APSwichAccountPopover.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APSwichAccountPopover.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/18. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APSwichAccountPopover: NSViewController { 12 | 13 | public var accounts = [Provider]() 14 | public var selectHandle: ((_ row: Int) -> Void)? 15 | 16 | @IBOutlet weak var tableView: NSTableView! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | tableView.delegate = self 21 | tableView.dataSource = self 22 | } 23 | 24 | @IBAction func cliedCancelBtn(_ sender: Any) { 25 | closeView() 26 | } 27 | 28 | func closeView() { 29 | guard let window = view.window, let parent = window.sheetParent 30 | else { return } 31 | parent.endSheet(window) 32 | } 33 | 34 | } 35 | 36 | extension APSwichAccountPopover: NSTableViewDelegate, NSTableViewDataSource { 37 | 38 | func numberOfRows(in tableView: NSTableView) -> Int { 39 | return accounts.count 40 | } 41 | 42 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 43 | if tableColumn?.identifier == NSUserInterfaceItemIdentifier(rawValue: "nameColumn") { 44 | let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "nameCell") 45 | guard let cellView = tableView.makeView(withIdentifier: cellIdentifier, owner: self) as? NSTableCellView else { return nil } 46 | cellView.textField?.stringValue = accounts[row].name 47 | return cellView 48 | } 49 | return nil 50 | } 51 | 52 | func tableViewSelectionDidChange(_ notification: Notification){ 53 | let tableView = notification.object as! NSTableView 54 | let clickedRow = tableView.selectedRow 55 | guard clickedRow >= 0 else { 56 | return 57 | } 58 | tableView.deselectRow(clickedRow) 59 | if let selectFunc = selectHandle { 60 | selectFunc(clickedRow) 61 | } 62 | closeView() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /AppleParty/AppListView/ScreenShotsView/ScreenShotUploadCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenShotUploadCell.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/2/25. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Foundation 11 | 12 | 13 | class ScreenShotDeleteCell: NSTableCellView { 14 | 15 | typealias CallFunc = (_ row: Int) -> Void 16 | var deleteCell: CallFunc? 17 | var row: Int = 0 18 | 19 | @IBOutlet weak var deleteBtn: NSButton! 20 | 21 | @IBAction func clickedDeleteBtn(_ sender: NSButton) { 22 | if let callBack = deleteCell { 23 | callBack(row) 24 | } 25 | } 26 | } 27 | 28 | class ScreenShotUploadCell: NSTableCellView { 29 | 30 | typealias CallBackHandler = (_ value: String, _ row: Int) -> Void 31 | var changeSortIndex: CallBackHandler? 32 | var changeVideoFrame: CallBackHandler? 33 | var row: Int = 0 34 | 35 | @IBOutlet weak var sortField: NSTextField! 36 | @IBOutlet weak var videoField: NSTextField! 37 | @IBOutlet weak var videoTitleField: NSTextField! 38 | 39 | @IBOutlet weak var cellTopConstraint: NSLayoutConstraint! 40 | 41 | override func awakeFromNib() { 42 | super.awakeFromNib() 43 | 44 | sortField.delegate = self 45 | videoField.delegate = self 46 | } 47 | 48 | func updateData(sort: String, frame: String) { 49 | sortField.stringValue = sort 50 | videoField.stringValue = frame 51 | } 52 | 53 | func showVideoView(_ show: Bool) { 54 | videoField.isHidden = !show 55 | videoTitleField.isHidden = !show 56 | cellTopConstraint.constant = show ? 10.0 : 20.0 57 | } 58 | 59 | } 60 | 61 | extension ScreenShotUploadCell: NSTextFieldDelegate { 62 | /// 内容改变 63 | func controlTextDidChange(_ obj: Notification) { 64 | let textField = obj.object as! NSTextField 65 | let value = textField.stringValue 66 | if textField.tag == sortField.tag, let callBack = changeSortIndex { 67 | callBack(value, row) 68 | } 69 | 70 | if textField.tag == videoField.tag, let callBack = changeVideoFrame { 71 | callBack(value, row) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /AppleParty/AppListView/InAppPurchseView/DragView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DragView.swift 3 | // AppleParty 4 | // 5 | // Created by 易承 on 2020/12/16. 6 | // 7 | 8 | import Cocoa 9 | 10 | protocol DragViewDelegate { 11 | func dragView(_ path: String?) 12 | } 13 | 14 | class DragView: NSView { 15 | 16 | var delegate: DragViewDelegate? 17 | 18 | private var fileTypeIsOk = false 19 | let NSFilenamesPboardType = NSPasteboard.PasteboardType("NSFilenamesPboardType") 20 | let fileTypes = ["jpg", "jpeg", "png"] 21 | var droppedFilePath: String? 22 | 23 | override func draw(_ dirtyRect: NSRect) { 24 | super.draw(dirtyRect) 25 | } 26 | 27 | required init?(coder: NSCoder) { 28 | super.init(coder: coder) 29 | // Declare and register an array of accepted types 30 | registerForDraggedTypes([NSPasteboard.PasteboardType(kUTTypeFileURL as String), 31 | NSPasteboard.PasteboardType(kUTTypeItem as String)]) 32 | } 33 | 34 | override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { 35 | fileTypeIsOk = checkExtension(drag: sender) 36 | return [] 37 | } 38 | 39 | override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation { 40 | return fileTypeIsOk ? .link : [] 41 | } 42 | 43 | override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { 44 | if let board = sender.draggingPasteboard.propertyList(forType: NSFilenamesPboardType) as? NSArray, let imagePath = board[0] as? String { 45 | // THIS IS WERE YOU GET THE PATH FOR THE DROPPED FILE 46 | droppedFilePath = imagePath 47 | if fileTypeIsOk { 48 | delegate?.dragView(droppedFilePath) 49 | } 50 | return true 51 | } 52 | return false 53 | } 54 | 55 | fileprivate func checkExtension(drag: NSDraggingInfo) -> Bool { 56 | if let board = drag.draggingPasteboard.propertyList(forType: NSFilenamesPboardType) as? NSArray, let path = board[0] as? String { 57 | let url = NSURL(fileURLWithPath: path) 58 | if let fileExtension = url.pathExtension?.lowercased() { 59 | return fileTypes.contains(fileExtension) 60 | } 61 | } 62 | return false 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /AppleParty/RootView/APRootVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APRootVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/11. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APRootVC: NSViewController { 12 | 13 | fileprivate var adapter: APRootCollectionAdapter? 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | configureCollectionView() 19 | } 20 | 21 | override func viewDidAppear() { 22 | super.viewDidAppear() 23 | 24 | if UserCenter.shared.isAutoLogin { 25 | let wc = self.view.window?.windowController as! APRootWC 26 | wc.clickedAccountItem(nil) 27 | } 28 | } 29 | 30 | /// 配置显示的功能列表 31 | func configureCollectionView() { 32 | let colview = APCollectionView() 33 | colview.configure(superView: view) 34 | 35 | adapter = APRootCollectionAdapter(collectionView: colview.collectionView) 36 | let handler = { (isShow: Bool, name: String) in 37 | if isShow { 38 | let mainStoryBoard = NSStoryboard(name: name, bundle: nil) 39 | let windowController = mainStoryBoard.instantiateController(withIdentifier: name) as! NSWindowController 40 | windowController.showWindow(self) 41 | 42 | } else { 43 | APHUD.hide(message: "功能暂未开源,敬请期待~", delayTime: 2) 44 | } 45 | } 46 | 47 | let items = [ 48 | APRootCollectionModel(name: "我的 App", icon: "Apps", handler: { handler(true, "AppList") }), 49 | APRootCollectionModel(name: "App 分析报表", icon: "AppAnalytics", handler: { handler(false, "") }), 50 | APRootCollectionModel(name: "财务报表", icon: "FinancialReports", handler: { handler(false, "") }), 51 | APRootCollectionModel(name: "邮件工具", icon: "SendEmail", handler: { handler(true, "EmailTool") }), 52 | APRootCollectionModel(name: "包体工具", icon: "IPAUpload", handler: { handler(true, "IPAUpload")}), 53 | APRootCollectionModel(name: "二维码工具", icon: "QRcode", handler: { handler(true, "APQRcode")}), 54 | APRootCollectionModel(name: "内购凭证验证", icon: "VerifyReceipt", handler: { handler(true, "APVerifyReceipt")}) 55 | ] 56 | adapter?.set(items: items) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /AppleParty/RootView/APRootCollectionAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APRootCollectionAdapter.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/14. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APRootCollectionAdapter: NSObject { 12 | fileprivate static let numberOfSections = 1 13 | fileprivate static let itemId = "APRootCollectionCell" 14 | 15 | fileprivate var items = [APRootCollectionModel]() { 16 | didSet { 17 | collectionView.reloadData() 18 | } 19 | } 20 | private var collectionView: NSCollectionView 21 | 22 | init(collectionView: NSCollectionView) { 23 | self.collectionView = collectionView 24 | super.init() 25 | self.collectionView.dataSource = self 26 | self.collectionView.delegate = self 27 | self.collectionView.register(APRootCollectionCell.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: APRootCollectionAdapter.itemId)) 28 | } 29 | 30 | func set(items: [APRootCollectionModel]) { 31 | self.items = items 32 | } 33 | } 34 | 35 | 36 | extension APRootCollectionAdapter: NSCollectionViewDataSource, NSCollectionViewDelegate { 37 | func numberOfSectionsInCollectionView(collectionView: NSCollectionView) -> Int { 38 | return APRootCollectionAdapter.numberOfSections 39 | } 40 | 41 | func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { 42 | return items.count 43 | } 44 | 45 | func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { 46 | let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: APRootCollectionAdapter.itemId), for: indexPath) 47 | guard let collectionViewItem = item as? APRootCollectionCell else { return item } 48 | 49 | let name = items[indexPath.item].name 50 | let icon = items[indexPath.item].icon 51 | collectionViewItem.configure(name: name, icon: icon) 52 | return item 53 | } 54 | 55 | func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set) { 56 | collectionView.deselectItems(at: indexPaths) 57 | if let item = indexPaths.first?.item, let handler = items[item].handler { 58 | handler() 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | Pods/ 58 | 59 | 60 | # Carthage 61 | # 62 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 63 | # Carthage/Checkouts 64 | 65 | Carthage/Build 66 | 67 | 68 | # Accio dependency management 69 | Dependencies/ 70 | .accio/ 71 | 72 | # fastlane 73 | # 74 | # It is recommended to not store the screenshots in the git repo. 75 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 76 | # For more information about the recommended setup visit: 77 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 78 | 79 | fastlane/report.xml 80 | fastlane/Preview.html 81 | fastlane/screenshots/**/*.png 82 | fastlane/test_output 83 | 84 | # Code Injection 85 | # 86 | # After new code Injection tools there's a generated folder /iOSInjectionProject 87 | # https://github.com/johnno1962/injectionforxcode 88 | 89 | iOSInjectionProject/ 90 | 91 | 92 | ## Other 93 | *.xcuserstate 94 | 95 | ## Obj-C/Swift specific 96 | *.hmap 97 | *.ipa 98 | *.swp 99 | .DS_Store -------------------------------------------------------------------------------- /AppleParty/Shared/UI/APCollectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APCollectionView.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/14. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APCollectionView: NSView { 12 | 13 | lazy var scrollView: NSScrollView = { 14 | let scrollView = NSScrollView() 15 | let margin: CGFloat = 20 16 | scrollView.automaticallyAdjustsContentInsets = false 17 | scrollView.contentInsets = NSEdgeInsetsMake(0, margin, 0, margin) 18 | scrollView.scrollerInsets = NSEdgeInsetsMake(0, 0, 0, -margin) 19 | return scrollView 20 | }() 21 | 22 | lazy var collectionView: NSCollectionView = { 23 | let itemWidth = CGFloat(150.0) 24 | let itemHeight = CGFloat(150.0) 25 | let itemSpacing = CGFloat(100.0) 26 | let itemPadding = CGFloat(50.0) 27 | 28 | let flowLayout = NSCollectionViewFlowLayout() 29 | flowLayout.scrollDirection = .vertical 30 | flowLayout.itemSize = NSMakeSize(itemWidth, itemHeight) 31 | flowLayout.minimumInteritemSpacing = itemSpacing 32 | flowLayout.minimumLineSpacing = itemSpacing 33 | flowLayout.sectionInset = NSEdgeInsetsMake(itemPadding, itemPadding, itemPadding, itemPadding) 34 | 35 | let collection = NSCollectionView() 36 | collection.collectionViewLayout = flowLayout 37 | collection.isSelectable = true 38 | return collection 39 | }() 40 | 41 | init() { 42 | super.init(frame: .zero) 43 | addSubviews() 44 | addConstraints() 45 | } 46 | 47 | required init?(coder: NSCoder) { 48 | fatalError("init(coder:) has not been implemented") 49 | } 50 | } 51 | 52 | extension APCollectionView { 53 | 54 | func configure(superView: NSView) { 55 | superView.addSubview(self) 56 | self.translatesAutoresizingMaskIntoConstraints = false 57 | NSLayoutConstraint.activate([ 58 | self.topAnchor.constraint(equalTo: superView.topAnchor), 59 | self.leadingAnchor.constraint(equalTo: superView.leadingAnchor), 60 | self.trailingAnchor.constraint(equalTo: superView.trailingAnchor), 61 | self.bottomAnchor.constraint(equalTo: superView.bottomAnchor) 62 | ]) 63 | } 64 | 65 | private func addSubviews() { 66 | scrollView.documentView = collectionView 67 | [scrollView].forEach(addSubview) 68 | } 69 | 70 | private func addConstraints() { 71 | scrollView.translatesAutoresizingMaskIntoConstraints = false 72 | NSLayoutConstraint.activate([ 73 | scrollView.topAnchor.constraint(equalTo: scrollView.superview!.topAnchor), 74 | scrollView.leadingAnchor.constraint(equalTo: scrollView.superview!.leadingAnchor), 75 | scrollView.trailingAnchor.constraint(equalTo: scrollView.superview!.trailingAnchor), 76 | scrollView.bottomAnchor.constraint(equalTo: scrollView.superview!.bottomAnchor) 77 | ]) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /AppleParty/AppListView/APAppListAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APAppListAdapter.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/17. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APAppListAdapter: NSObject { 12 | 13 | public var purchseHandle: ((_ app: App) -> Void)? 14 | public var screenshotHandle: ((_ app: App) -> Void)? 15 | 16 | fileprivate static let numberOfSections = 1 17 | fileprivate static let itemId = "APAppListCell" 18 | 19 | fileprivate var items = [App]() { 20 | didSet { 21 | collectionView.reloadData() 22 | } 23 | } 24 | private var collectionView: NSCollectionView 25 | 26 | init(collectionView: NSCollectionView) { 27 | self.collectionView = collectionView 28 | super.init() 29 | self.collectionView.dataSource = self 30 | self.collectionView.delegate = self 31 | self.collectionView.register(APAppListCell.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: APAppListAdapter.itemId)) 32 | 33 | let itemWidth = CGFloat(350.0) 34 | let itemHeight = CGFloat(150.0) 35 | let itemSpacing = CGFloat(80.0) 36 | let itemPadding = CGFloat(30.0) 37 | 38 | let flowLayout = NSCollectionViewFlowLayout() 39 | flowLayout.scrollDirection = .vertical 40 | flowLayout.itemSize = NSMakeSize(itemWidth, itemHeight) 41 | flowLayout.minimumInteritemSpacing = itemSpacing 42 | flowLayout.minimumLineSpacing = itemSpacing 43 | flowLayout.sectionInset = NSEdgeInsetsMake(itemPadding, itemPadding, itemPadding, itemPadding) 44 | self.collectionView.collectionViewLayout = flowLayout 45 | } 46 | 47 | func set(items: [App]) { 48 | self.items = items 49 | } 50 | } 51 | 52 | 53 | extension APAppListAdapter: NSCollectionViewDataSource, NSCollectionViewDelegate { 54 | func numberOfSectionsInCollectionView(collectionView: NSCollectionView) -> Int { 55 | return APAppListAdapter.numberOfSections 56 | } 57 | 58 | func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { 59 | return items.count 60 | } 61 | 62 | func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { 63 | let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: APAppListAdapter.itemId), for: indexPath) 64 | guard let collectionViewItem = item as? APAppListCell else { return item } 65 | 66 | collectionViewItem.configure(app: items[indexPath.item]) 67 | collectionViewItem.purchseHandle = purchseHandle 68 | collectionViewItem.screenshotHandle = screenshotHandle 69 | 70 | return item 71 | } 72 | 73 | func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set) { 74 | collectionView.deselectItems(at: indexPaths) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /AppleParty/VerifyReceipt/APVerifyReceiptVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APVerifyReceiptVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/6/1. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APVerifyReceiptVC: NSViewController { 12 | 13 | @IBOutlet weak var sharedSecretField: NSTextField! 14 | @IBOutlet weak var apiTypeMatrix: NSMatrix! 15 | @IBOutlet weak var receiptTextView: NSTextView! 16 | @IBOutlet weak var responseTextView: NSTextView! 17 | @IBOutlet weak var verifyButton: NSButton! 18 | 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | } 23 | 24 | 25 | @IBAction func clickedVerifyButtion(_ sender: Any) { 26 | 27 | let receiptString = receiptTextView.string 28 | if receiptString.isEmpty { 29 | APHUD.hide(message: "请填写 receipt base64 字符串~", delayTime: 1) 30 | return 31 | } 32 | 33 | responseTextView.string = "" 34 | APHUD.show(message: "请求中...") 35 | 36 | let sharedSecretString = sharedSecretField.stringValue 37 | var apiUrl = "" 38 | let apiType = apiTypeMatrix.selectedRow 39 | if apiType == 0 { 40 | apiUrl = "https://sandbox.itunes.apple.com/verifyReceipt" 41 | } 42 | else if (apiType == 1) { 43 | apiUrl = "https://buy.itunes.apple.com/verifyReceipt" 44 | } 45 | 46 | // unused : exclude-old-transactions 47 | var json: [String: Any] = ["receipt-data": receiptString] 48 | if !sharedSecretString.isEmpty { 49 | json["password"] = sharedSecretString 50 | } 51 | let jsonData = try? JSONSerialization.data(withJSONObject: json) 52 | 53 | // create post request 54 | let url = URL(string: apiUrl)! 55 | var request = URLRequest(url: url) 56 | request.httpMethod = "POST" 57 | request.timeoutInterval = 15 58 | request.setValue("\(String(describing: jsonData?.count))", forHTTPHeaderField: "Content-Length") 59 | request.setValue("application/json", forHTTPHeaderField: "Content-Type") 60 | request.httpBody = jsonData 61 | 62 | let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in 63 | APHUD.hide() 64 | guard let data = data, error == nil else { 65 | let msg = error?.localizedDescription ?? "未知错误" 66 | APHUD.hide(message: msg, delayTime: 1) 67 | print(msg) 68 | return 69 | } 70 | let responseJSON = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) 71 | if let responseJSON = responseJSON as? [String: Any] { 72 | DispatchQueue.main.async { 73 | self?.responseTextView.string = String(format: "\(responseJSON)") 74 | } 75 | } else { 76 | APHUD.hide(message: "数据解析错误~", delayTime: 1) 77 | } 78 | } 79 | 80 | task.resume() 81 | 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /AppleParty.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "9d0d9b5cf3459d54c921a64b1bb7a803edf7e8255bc0e8227a135b3820aeee10", 3 | "pins" : [ 4 | { 5 | "identity" : "appstoreconnect-swift-sdk", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/AvdLee/appstoreconnect-swift-sdk.git", 8 | "state" : { 9 | "revision" : "78b2be2f68f30141fca2f7bce45ca7866535cf28", 10 | "version" : "4.0.2" 11 | } 12 | }, 13 | { 14 | "identity" : "bluecryptor", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/Kitura/BlueCryptor.git", 17 | "state" : { 18 | "revision" : "cec97c24b111351e70e448972a7d3fe68a756d6d", 19 | "version" : "2.0.2" 20 | } 21 | }, 22 | { 23 | "identity" : "bluesocket", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/Kitura/BlueSocket.git", 26 | "state" : { 27 | "revision" : "7b23a867008e0027bfd6f4d398d44720707bc8ca", 28 | "version" : "2.0.4" 29 | } 30 | }, 31 | { 32 | "identity" : "bluesslservice", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/Kitura/BlueSSLService.git", 35 | "state" : { 36 | "revision" : "b27a94d063962dfa1bba9f79814c4ef202cf33a4", 37 | "version" : "2.0.2" 38 | } 39 | }, 40 | { 41 | "identity" : "loggerapi", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/Kitura/LoggerAPI.git", 44 | "state" : { 45 | "revision" : "e82d34eab3f0b05391082b11ea07d3b70d2f65bb", 46 | "version" : "1.9.200" 47 | } 48 | }, 49 | { 50 | "identity" : "swift-asn1", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/apple/swift-asn1.git", 53 | "state" : { 54 | "revision" : "f70225981241859eb4aa1a18a75531d26637c8cc", 55 | "version" : "1.4.0" 56 | } 57 | }, 58 | { 59 | "identity" : "swift-crypto", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/apple/swift-crypto.git", 62 | "state" : { 63 | "revision" : "95ba0316a9b733e92bb6b071255ff46263bbe7dc", 64 | "version" : "3.15.1" 65 | } 66 | }, 67 | { 68 | "identity" : "swift-log", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/apple/swift-log.git", 71 | "state" : { 72 | "revision" : "6fe203dc33195667ce1759bf0182975e4653ba1c", 73 | "version" : "1.4.4" 74 | } 75 | }, 76 | { 77 | "identity" : "swift-smtp", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/Kitura/Swift-SMTP", 80 | "state" : { 81 | "revision" : "4b7666bb8cee33f0cb367786af17b9a2ebb63047", 82 | "version" : "6.0.0" 83 | } 84 | }, 85 | { 86 | "identity" : "urlqueryencoder", 87 | "kind" : "remoteSourceControl", 88 | "location" : "https://github.com/CreateAPI/URLQueryEncoder.git", 89 | "state" : { 90 | "revision" : "4ce950479707ea109f229d7230ec074a133b15d7", 91 | "version" : "0.2.1" 92 | } 93 | } 94 | ], 95 | "version" : 3 96 | } 97 | -------------------------------------------------------------------------------- /AppleParty/AppListView/Base.lproj/AppList.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /AppleParty/Shared/Utils/EmailUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmailUtils.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2021/9/09. 6 | // Copyright © 2021 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | import Foundation 11 | import SwiftSMTP 12 | 13 | 14 | class EmailUtils: NSObject { 15 | static func performSend(subject: String, recipients: [String], items: [Any]) { 16 | let service = NSSharingService(named: NSSharingService.Name.composeEmail)! 17 | service.recipients = recipients 18 | service.subject = subject 19 | service.perform(withItems: items) 20 | } 21 | 22 | static func autoSend(subject: String, recipients: [String], htmlContent: String, _ textContent: String = "", attachmentPath: String = "", config: EamilConfigs, retry: Int = 3, completion: ((Error?) -> Void)? = nil) { 23 | autoSendAtts(subject: subject, recipients: recipients, htmlContent: htmlContent, textContent, attachmentFiles: [attachmentPath], config: config, retry: retry, completion: completion) 24 | } 25 | 26 | static func autoSendAtts(subject: String, recipients: [String], htmlContent: String, _ textContent: String = "", attachmentFiles: Array = [], config: EamilConfigs, retry: Int = 3, completion: ((Error?) -> Void)? = nil) { 27 | 28 | let smtp = SMTP( 29 | hostname: config.smtp, // SMTP server address 30 | email: config.addr, // username to login 31 | password: config.pwd // password to login 32 | ) 33 | 34 | let drLight = Mail.User(name: config.name, email: config.addr) 35 | var megamans = [Mail.User]() 36 | for email in recipients { 37 | let megaman = Mail.User(name: "", email: email) 38 | megamans.append(megaman) 39 | } 40 | // Create an HTML `Attachment` 41 | let htmlAttachment = Attachment( 42 | htmlContent: htmlContent 43 | // To reference `fileAttachment` 44 | ) 45 | 46 | var attachments: [Attachment] = [] 47 | attachments.append(htmlAttachment) 48 | 49 | // Create a file `Attachment` 50 | attachmentFiles.forEach { filePath in 51 | if !filePath.isEmpty { 52 | let fileAttachment = Attachment(filePath: filePath) 53 | attachments.append(fileAttachment) 54 | } 55 | } 56 | 57 | let mail = Mail( 58 | from: drLight, 59 | to: megamans, 60 | subject: subject, 61 | text: textContent, 62 | attachments: attachments 63 | ) 64 | 65 | smtp.send(mail) { (error) in 66 | if let error = error { 67 | print("SendEmail error: \(error)") 68 | if retry > 0 { 69 | autoSendAtts(subject: subject, recipients: recipients, htmlContent: htmlContent, textContent, attachmentFiles: attachmentFiles, config: config, retry: retry - 1, completion: completion) 70 | return 71 | } 72 | } 73 | debugPrint("email success~") 74 | if let block = completion { 75 | block(error) 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /AppleParty/AppListView/APAppListModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APAppListModel.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/17. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - 游戏列表 12 | struct AppList { 13 | var games: [App] 14 | 15 | init(body: [String: Any]) { 16 | games = [App]() 17 | let included = dictionaryArray(body["included"]) 18 | let apps = dictionaryArray(body["data"]) 19 | for software in apps { 20 | var game = App() 21 | let attributes = dictionary(software["attributes"]) 22 | game.appId = string(from: software["id"]) 23 | game.appName = string(from: attributes["name"]) 24 | game.platforms = string(from: attributes["distributionType"]) 25 | game.bundleId = string(from: attributes["bundleId"]) 26 | game.sku = string(from: attributes["sku"]) 27 | game.primaryLocale = string(from: attributes["primaryLocale"]) 28 | // icon 处理 29 | let appVersion = dictionaryArray( dictionary( dictionary(software["relationships"])["appStoreVersions"])["data"]).first 30 | if let version = appVersion { 31 | let vid = string(from: version["id"]) 32 | for info in included { 33 | let iid = string(from: info["id"]) 34 | if vid == iid, vid.count > 0 { 35 | let info_att = dictionary(info["attributes"]) 36 | let storeIcon = dictionary(info_att["storeIcon"]) 37 | let templateUrl = string(from: storeIcon["templateUrl"]) 38 | if templateUrl.count > 0 { 39 | game.iconUrl = templateUrl.replacingOccurrences(of: "{w}x{h}bb.{f}", with: "500x500bb.png") 40 | } 41 | break 42 | } 43 | } 44 | } 45 | games.append(game) 46 | } 47 | games = games.sorted(by: { (g1, g2) -> Bool in 48 | g1.appName < g2.appName 49 | }) 50 | } 51 | } 52 | 53 | struct App { 54 | var appId: String = "" 55 | var appName: String = "" 56 | var platforms: String = "" 57 | var iconUrl: String = "" 58 | var bundleId: String = "" 59 | var sku: String = "" 60 | var primaryLocale: String = "" 61 | } 62 | 63 | struct AppInfo { 64 | var name: String = "" 65 | var bundleId: String = "" 66 | var bundleIdReferenceName: String = "" 67 | var distributionType: String = "" 68 | var educationDiscountType: String = "" 69 | var sku: String = "" 70 | var primaryLocale: String = "" 71 | 72 | init(body: [String: Any]) { 73 | let data = dictionary(body["data"]) 74 | let attributes = dictionary(data["attributes"]) 75 | name = string(from: attributes["name"]) 76 | bundleId = string(from: attributes["bundleId"]) 77 | bundleIdReferenceName = string(from: attributes["bundleIdReferenceName"]) 78 | distributionType = string(from: attributes["distributionType"]) 79 | educationDiscountType = string(from: attributes["educationDiscountType"]) 80 | sku = string(from: attributes["sku"]) 81 | primaryLocale = string(from: attributes["primaryLocale"]) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /AppleParty/Shared/Utils/APUtil.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APUtil.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/14. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import KeychainAccess 11 | 12 | 13 | public struct Environment { 14 | public var keychain = APKeychain() 15 | public var defaults = APDefaults() 16 | } 17 | 18 | public var APUtil = Environment() 19 | 20 | /// refer: https://github.com/kishikawakatsumi/KeychainAccess 21 | public struct APKeychain { 22 | private static let keychain = KeychainAccess.Keychain(service: "com.37iOS.AppleParty") 23 | 24 | public func getString(_ key: String) throws -> String? { 25 | try APKeychain.keychain.getString(key) 26 | } 27 | 28 | public func set(_ value: String, key: String) throws { 29 | try APKeychain.keychain.set(value, key: key) 30 | } 31 | 32 | public func getData(_ key: String) throws -> Data? { 33 | try APKeychain.keychain.getData(key) 34 | } 35 | 36 | public func set(_ value: Data, key: String) throws { 37 | try APKeychain.keychain.set(value, key: key) 38 | } 39 | 40 | public func getDict(_ key: String) throws -> [String: String]? { 41 | if let data = try APKeychain.keychain.getData(key) { 42 | do { 43 | let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:String] 44 | return json 45 | } catch { 46 | print("APKeychain getDict something went wrong: \(error.localizedDescription)") 47 | } 48 | } 49 | return nil 50 | } 51 | 52 | public func setDict(_ dict: Dictionary, key: String) throws { 53 | let encoder = JSONEncoder() 54 | let jsonData = try encoder.encode(dict) 55 | try APKeychain.keychain.set(jsonData, key: key) 56 | } 57 | 58 | public func remove(_ key: String) throws -> Void { 59 | try APKeychain.keychain.remove(key) 60 | } 61 | } 62 | 63 | public struct APDefaults { 64 | public var string: (String) -> String? = { UserDefaults.standard.string(forKey: $0) } 65 | public func string(forKey key: String) -> String? { 66 | string(key) 67 | } 68 | 69 | public var date: (String) -> Date? = { Date(timeIntervalSince1970: UserDefaults.standard.double(forKey: $0)) } 70 | public func date(forKey key: String) -> Date? { 71 | date(key) 72 | } 73 | 74 | public var setDate: (Date?, String) -> Void = { UserDefaults.standard.set($0?.timeIntervalSince1970, forKey: $1) } 75 | public func setDate(_ value: Date?, forKey key: String) { 76 | setDate(value, key) 77 | } 78 | 79 | public var set: (Any?, String) -> Void = { UserDefaults.standard.set($0, forKey: $1) } 80 | public func set(_ value: Any?, forKey key: String) { 81 | set(value, key) 82 | } 83 | 84 | public var removeObject: (String) -> Void = { UserDefaults.standard.removeObject(forKey: $0) } 85 | public func removeObject(forKey key: String) { 86 | removeObject(key) 87 | } 88 | 89 | public var get: (String) -> Any? = { UserDefaults.standard.value(forKey: $0) } 90 | public func get(forKey key: String) -> Any? { 91 | get(key) 92 | } 93 | 94 | public var bool: (String) -> Bool? = { UserDefaults.standard.bool(forKey: $0) } 95 | public func bool(forKey key: String) -> Bool? { 96 | bool(key) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /AppleParty/Shared/UI/APASCKeysEditVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APSPasswordEditVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/5/18. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APASCKeysEditVC: NSViewController { 12 | 13 | @IBOutlet weak var titleLbl: NSTextField! 14 | @IBOutlet var accountTextView: NSTextField! 15 | @IBOutlet var issuerIDTextView: NSTextField! 16 | @IBOutlet var privateKeyIDTextView: NSTextField! 17 | @IBOutlet var privateKeyTextView: NSTextField! 18 | @IBOutlet weak var usePasswordBtn: NSButton! 19 | 20 | public var titleString: String? 21 | public var spassword: AppStoreConnectKey? 22 | public var updateCompletion: ((_ model: AppStoreConnectKey) -> Void)? 23 | 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | if let text = titleString { 29 | titleLbl.stringValue = text 30 | } 31 | 32 | if let model = spassword { 33 | accountTextView.stringValue = model.aliasName 34 | issuerIDTextView.stringValue = model.issuerID 35 | privateKeyIDTextView.stringValue = model.privateKeyID 36 | privateKeyTextView.stringValue = model.privateKey 37 | usePasswordBtn.state = model.isused ? .on : .off 38 | } else { 39 | // 新建时,默认读取当前开发者名称 40 | accountTextView.stringValue = UserCenter.shared.developerName 41 | } 42 | } 43 | 44 | @IBAction func clickedInputFileBtn(_ sender: Any) { 45 | let openPanel = NSOpenPanel() 46 | openPanel.canChooseFiles = true 47 | openPanel.canChooseDirectories = false 48 | openPanel.allowsMultipleSelection = false 49 | openPanel.allowedFileTypes = ["p8", "P8"] 50 | 51 | openPanel.beginSheetModal(for: self.view.window!) { (modalResponse) in 52 | if modalResponse == .OK { 53 | if let fileURL = openPanel.url { 54 | self.handleP8fileContent(fileURL) 55 | } 56 | } 57 | } 58 | } 59 | 60 | @IBAction func clickedCancelBtn(_ sender: Any) { 61 | dismiss(self) 62 | } 63 | 64 | @IBAction func clickedSaveBtn(_ sender: Any) { 65 | let account = accountTextView.stringValue.trim() 66 | let issuerID = issuerIDTextView.stringValue.trim() 67 | let privateKeyID = privateKeyIDTextView.stringValue.trim() 68 | let privateKey = privateKeyTextView.stringValue.trim() 69 | 70 | guard account.isNotEmpty, issuerID.isNotEmpty, privateKeyID.isNotEmpty, privateKey.isNotEmpty else { 71 | APHUD.hide(message: "所有配置项不能为空!", view: view, delayTime: 1) 72 | return 73 | } 74 | 75 | if let block = updateCompletion { 76 | block(AppStoreConnectKey(aliasName: account, issuerID: issuerID, privateKeyID: privateKeyID, privateKey: privateKey, isused: usePasswordBtn.state == .on)) 77 | } 78 | dismiss(self) 79 | } 80 | 81 | func handleP8fileContent(_ url: URL) { 82 | guard let data = try? Data(contentsOf: url), 83 | var content = String(data: data, encoding: .utf8) else { 84 | APHUD.hide(message: "p8文件内容读取失败!请检查文件是否完整!", delayTime: 1) 85 | return 86 | } 87 | let begin = "-----BEGIN PRIVATE KEY-----" 88 | let end = "-----END PRIVATE KEY-----" 89 | content = content.replacingOccurrences(of: begin, with: "") 90 | content = content.replacingOccurrences(of: end, with: "") 91 | content = content.replacingOccurrences(of: "\n", with: "") 92 | privateKeyTextView.stringValue = content 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /AppleParty/AppListView/InAppPurchseView/IAPUploadImageVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UploadVC.swift 3 | // AppleParty 4 | // 5 | // Created by 易承 on 2021/6/3. 6 | // 7 | 8 | import AppKit 9 | import Foundation 10 | 11 | class IAPUploadImageVC: NSViewController { 12 | 13 | @IBOutlet weak var tableView: NSTableView! 14 | 15 | @IBOutlet weak var cancelBtm: NSButton! 16 | @IBOutlet weak var submitBtm: NSButton! 17 | @IBOutlet weak var tipLb: NSTextField! 18 | var picnames = [String]() 19 | var resultPaths = [String: String]() 20 | 21 | typealias CallBackFunc = (_ paths: [String: String]) -> Void 22 | var callBackFunc: CallBackFunc? 23 | 24 | fileprivate lazy var fileTypes: [String] = { 25 | return ["jpg", "jpeg", "png"] 26 | }() 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | 31 | tableView.delegate = self 32 | tableView.dataSource = self 33 | tipLb.stringValue = "需要上传\(picnames.count)张图片" 34 | } 35 | 36 | @IBAction func clickedBatchUploadBtn(_ sender: Any) { 37 | let openPanel = NSOpenPanel() 38 | openPanel.canChooseFiles = true 39 | openPanel.canChooseDirectories = false 40 | openPanel.allowsMultipleSelection = true 41 | openPanel.allowedFileTypes = fileTypes 42 | openPanel.beginSheetModal(for: self.view.window!) { (modalResponse: NSApplication.ModalResponse) in 43 | if modalResponse == .OK { 44 | openPanel.urls.forEach { url in 45 | let picname = url.lastPathComponent 46 | debugPrint(picname) 47 | if self.picnames.contains(picname) { 48 | debugPrint("contains") 49 | self.resultPaths[picname] = url.path 50 | } 51 | } 52 | self.tableView.reloadData() 53 | } 54 | } 55 | } 56 | 57 | 58 | @IBAction func cancel(_ sender: Any) { 59 | dismiss(self) 60 | } 61 | 62 | @IBAction func submit(_ sender: Any) { 63 | guard picnames.count == resultPaths.keys.count else { 64 | APHUD.hide(message: "必须图片数量不正确!", view: self.view) 65 | return 66 | } 67 | dismiss(self) 68 | if let callBackFunc = callBackFunc { 69 | callBackFunc(resultPaths) 70 | } 71 | } 72 | } 73 | 74 | extension IAPUploadImageVC: NSTableViewDelegate, NSTableViewDataSource { 75 | func numberOfRows(in tableView: NSTableView) -> Int { 76 | return picnames.count 77 | } 78 | 79 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 80 | switch tableColumn?.identifier.enumValue() { 81 | case ColumnIdetifier.picname.cellValue: 82 | let cell = tableView.makeView(withIdentifier: ColumnIdetifier.picname.cellValue, owner: self) as? NSTableCellView 83 | cell?.textField?.stringValue = picnames[row] 84 | return cell 85 | case ColumnIdetifier.upload.cellValue: 86 | let cell = tableView.makeView(withIdentifier: ColumnIdetifier.upload.cellValue, owner: self) as? UploadCell 87 | cell?.row = row 88 | cell?.dragView(resultPaths[picnames[row]]) 89 | cell?.callBackFunc = { path,crow in 90 | self.resultPaths[self.picnames[crow]] = path 91 | } 92 | return cell 93 | default: 94 | return nil 95 | } 96 | } 97 | 98 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 99 | 100 100 | } 101 | 102 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { 103 | return false 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /AppleParty/Vendors/ITMS/XMLManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLManager.swift 3 | // AppleParty 4 | // 5 | // Created by 易承 on 2021/5/25. 6 | // 7 | 8 | import Foundation 9 | 10 | class XMLManager { 11 | 12 | static let appPtah = "/AppleParty/InAppPurches/" 13 | static let shotPtah = "/AppleParty/ScreenShots/" 14 | static let ipaPtah = "/AppleParty/UploadIpa/" 15 | 16 | static func getITMSPath(_ appid: String) -> String { 17 | return getFilePath(appid, filePath: appPtah) 18 | } 19 | 20 | static func getShotsPath(_ appid: String) -> String { 21 | return getFilePath(appid, filePath: shotPtah) 22 | } 23 | 24 | static func getIpaPath(_ appid: String) -> String { 25 | return getFilePath(appid, filePath: ipaPtah) 26 | } 27 | 28 | static func getFilePath(_ appid: String, filePath: String) -> String { 29 | let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) 30 | let documentsDirectory = paths[0] 31 | let filePath = documentsDirectory + filePath + appid + ".itmsp" 32 | return filePath 33 | } 34 | 35 | static func verifyITMS(account: String, pwd: String, filePath: String) -> (Int32, String?) { 36 | return runShellWithArgsAndOutput(launchPath: iTMSTransporter, "-f", filePath, "-m", "verify", "-u", account, "-p", pwd) 37 | } 38 | 39 | static func uploadITMS(account: String, pwd: String, filePath: String) -> (Int32, String?) { 40 | return runShellWithArgsAndOutput(launchPath: iTMSTransporter, "-f", filePath, "-m", "upload", "-u", account, "-p", pwd) 41 | } 42 | 43 | static func deleteITMS(_ filePath:String) { 44 | do { 45 | let fileManager = FileManager.default 46 | // Check if file or directory exists 47 | if fileManager.fileExists(atPath: filePath) { 48 | // Delete file or directory 49 | try fileManager.removeItem(atPath: filePath) 50 | } else { 51 | print("File does not exist: \(filePath)") 52 | } 53 | } catch { 54 | print("File An error took place: \(error)") 55 | } 56 | } 57 | 58 | static func openDocuments() { 59 | let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) 60 | let documentsDirectory = paths[0] 61 | runShellWithArgs(launchPath: "open", documentsDirectory) 62 | } 63 | 64 | static func copySimpleExel() { 65 | if let xlsxPath = Bundle.main.path(forResource: "example", ofType: "xlsx") { 66 | let dateFormatter : DateFormatter = DateFormatter() 67 | dateFormatter.dateFormat = "yyyyMMdd_HHmmss" 68 | let currentDate = dateFormatter.string(from: Date()) 69 | try? FileManager.default.copyItem(atPath: xlsxPath, toPath: NSHomeDirectory()+"/Desktop/IAP-\(currentDate).xlsx") 70 | } 71 | runShellWithArgs(launchPath: "/usr/bin/open", NSHomeDirectory()+"/Desktop/") 72 | } 73 | } 74 | 75 | // MARK: - Shell命令 76 | let iTMSTransporter = "/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter" 77 | let shell = "/usr/bin/env" 78 | 79 | @discardableResult 80 | func runShellWithArgs(launchPath: String, _ args: String...) -> Int32 { 81 | let task = Process() 82 | task.launchPath = launchPath 83 | task.arguments = args 84 | task.launch() 85 | task.waitUntilExit() 86 | return task.terminationStatus 87 | } 88 | 89 | func runShellWithArgsAndOutput(launchPath: String, _ args: String...) -> (Int32, String?) { 90 | let task = Process() 91 | task.launchPath = launchPath 92 | task.arguments = args 93 | let pipe = Pipe() 94 | task.standardOutput = pipe 95 | task.standardError = pipe 96 | task.launch() 97 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 98 | let output = String(data: data, encoding: .utf8) 99 | task.waitUntilExit() 100 | return (task.terminationStatus, output) 101 | } 102 | -------------------------------------------------------------------------------- /AppleParty/Vendors/QrcodeUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // QrcodeUtil.m 3 | // 4 | // Created by HTC on 2019/11/25. 5 | // 6 | 7 | #import 8 | #import 9 | #import 10 | 11 | NSDictionary* scanQRCodeOnScreen() { 12 | /* displays[] Quartz display ID's */ 13 | CGDirectDisplayID *displays = nil; 14 | 15 | CGError err = CGDisplayNoErr; 16 | CGDisplayCount dspCount = 0; 17 | 18 | /* How many active displays do we have? */ 19 | err = CGGetActiveDisplayList(0, NULL, &dspCount); 20 | 21 | /* If we are getting an error here then their won't be much to display. */ 22 | if(err != CGDisplayNoErr) 23 | { 24 | NSLog(@"Could not get active display count (%d)\n", err); 25 | return @{@"qrcode": @[], @"message": [NSString stringWithFormat:@"Could not get active display count (%d).", err]};; 26 | } 27 | 28 | /* Allocate enough memory to hold all the display IDs we have. */ 29 | displays = calloc((size_t)dspCount, sizeof(CGDirectDisplayID)); 30 | 31 | // Get the list of active displays 32 | err = CGGetActiveDisplayList(dspCount, 33 | displays, 34 | &dspCount); 35 | 36 | /* More error-checking here. */ 37 | if(err != CGDisplayNoErr) 38 | { 39 | NSLog(@"Could not get active display list (%d)\n", err); 40 | return @{@"qrcode": @[], @"message": [NSString stringWithFormat:@"Could not get active display list (%d).", err]};; 41 | } 42 | 43 | NSMutableArray* foundSSUrls = [NSMutableArray array]; 44 | 45 | CIDetector *detector = [CIDetector detectorOfType:@"CIDetectorTypeQRCode" 46 | context:nil 47 | options:@{ CIDetectorAccuracy:CIDetectorAccuracyHigh }]; 48 | 49 | for (unsigned int displaysIndex = 0; displaysIndex < dspCount; displaysIndex++) 50 | { 51 | /* Make a snapshot image of the current display. */ 52 | CGImageRef image = CGDisplayCreateImage(displays[displaysIndex]); 53 | NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image]]; 54 | for (CIQRCodeFeature *feature in features) { 55 | [foundSSUrls addObject:feature.messageString]; 56 | } 57 | CGImageRelease(image); 58 | } 59 | 60 | free(displays); 61 | 62 | return @{@"qrcode": foundSSUrls, @"message": @"success"}; 63 | } 64 | 65 | NSImage* createQRImage(NSString *string, NSSize size) { 66 | NSImage *outputImage = [[NSImage alloc]initWithSize:size]; 67 | [outputImage lockFocus]; 68 | 69 | // Setup the QR filter with our string 70 | CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"]; 71 | [filter setDefaults]; 72 | 73 | NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; 74 | [filter setValue:data forKey:@"inputMessage"]; 75 | /* 76 | L: 7% 77 | M: 15% 78 | Q: 25% 79 | H: 30% 80 | */ 81 | [filter setValue:@"Q" forKey:@"inputCorrectionLevel"]; 82 | 83 | CIImage *image = [filter valueForKey:@"outputImage"]; 84 | 85 | // Calculate the size of the generated image and the scale for the desired image size 86 | CGRect extent = CGRectIntegral(image.extent); 87 | CGFloat scale = MIN(size.width / CGRectGetWidth(extent), size.height / CGRectGetHeight(extent)); 88 | 89 | CGImageRef bitmapImage = [NSGraphicsContext.currentContext.CIContext createCGImage:image fromRect:extent]; 90 | 91 | CGContextRef graphicsContext = NSGraphicsContext.currentContext.CGContext; 92 | 93 | CGContextSetInterpolationQuality(graphicsContext, kCGInterpolationNone); 94 | CGContextScaleCTM(graphicsContext, scale, scale); 95 | CGContextDrawImage(graphicsContext, extent, bitmapImage); 96 | 97 | // Cleanup 98 | CGImageRelease(bitmapImage); 99 | 100 | [outputImage unlockFocus]; 101 | return outputImage; 102 | } 103 | -------------------------------------------------------------------------------- /AppleParty/RootView/APRootCollectionCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /AppleParty/Shared/Info/UserCenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserCenter.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/17. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | struct User { 13 | var appleid: String 14 | var password: String 15 | } 16 | 17 | struct Provider { 18 | var name: String 19 | var providerId: String 20 | var publicProviderId: String 21 | } 22 | 23 | /// 专用密码模型 24 | struct SPassword: Codable { 25 | var account: String 26 | var password: String 27 | var isused: Bool 28 | 29 | mutating func model(_ sp: SPassword, _ isused: Bool) -> SPassword { 30 | SPassword(account: sp.account, password: sp.password, isused: isused) 31 | } 32 | } 33 | 34 | struct SPasswordModel: Codable { 35 | /// 数据存储 36 | var list: [SPassword] 37 | } 38 | 39 | private let UserCenterKey_HistoryUser_Key = "UserCenterKey_HistoryUser_Key" 40 | private let UserCenterKey_Developer_Key = "UserCenterKey_Developer_Key" 41 | private let UserCenterKey_AutoLogin_Key = "UserCenterKey_AutoLogin_Key" 42 | private let UserCenterKey_AppStoreConnect_Key = "UserCenterKey_AppStoreConnect_Key" 43 | 44 | struct UserCenter { 45 | static var shared = UserCenter() 46 | /// 账号id 47 | var accountPrsId = "" 48 | /// 账号邮箱 49 | var accountEmail = "" 50 | /// 账号所有子账号信息 51 | var accountProviders: [String: Any] = [:] 52 | /// 账号登陆态是否有效 53 | var isAuthorized = false 54 | 55 | 56 | // MARK: - 历史登录用户 57 | lazy var historyUser: [User] = { 58 | if let value = try? APUtil.keychain.getString(UserCenterKey_HistoryUser_Key) { 59 | let array = value.components(separatedBy: "|") 60 | var result = [User]() 61 | for temp in array { 62 | result.append(User(appleid: temp.components(separatedBy: "_").first ?? "", password: temp.components(separatedBy: "_").last ?? "")) 63 | } 64 | return result 65 | } 66 | return [] 67 | }() 68 | 69 | // MARK: - 登录用户 70 | var loginedUser: User { 71 | mutating get { 72 | return historyUser.first ?? User(appleid: "", password: "") 73 | } 74 | set { 75 | historyUser = historyUser.filter{ $0.appleid != newValue.appleid } 76 | historyUser.insert(newValue, at: 0) 77 | let value = historyUser.map { $0.appleid + "_" + $0.password }.joined(separator: "|") 78 | try? APUtil.keychain.set(value, key: UserCenterKey_HistoryUser_Key) 79 | } 80 | } 81 | 82 | 83 | // MARK: - 开发者信息 84 | /// 开发者 id 85 | var developerId = "" 86 | /// 新的开发者id 87 | var publicDeveloperId = "" 88 | /// 开发者名字 89 | var developerName = "" 90 | /// 开发者团队 id 91 | var developerTeamId = "" 92 | /// 苹果账号专用密码 93 | var currentSPassword: SPassword? { 94 | get { 95 | let accounts = self.secondaryPasswordList 96 | let models = accounts.filter({ $0.isused == true }) 97 | return models.first 98 | } 99 | } 100 | // 所有的专用密码 101 | var secondaryPasswordList: [SPassword] { 102 | set { 103 | let encoder = JSONEncoder() 104 | encoder.outputFormatting = .prettyPrinted 105 | if let data = try? encoder.encode(SPasswordModel(list: newValue)) { 106 | try? APUtil.keychain.set(data, key: UserCenterKey_Developer_Key) 107 | } 108 | } 109 | get { 110 | if let listData = try? APUtil.keychain.getData(UserCenterKey_Developer_Key), 111 | let model = try? JSONDecoder().decode(SPasswordModel.self, from: listData) { 112 | return model.list 113 | } else { 114 | return [] 115 | } 116 | } 117 | } 118 | 119 | // MARK: - 自动登录态 120 | var isFirstTime = true 121 | var isAutoLogin: Bool { 122 | get { 123 | return APUtil.defaults.bool(forKey: UserCenterKey_AutoLogin_Key) ?? false 124 | } 125 | set { 126 | APUtil.defaults.set(newValue, forKey: UserCenterKey_AutoLogin_Key) 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Apple Party(苹果派) 3 | 4 | AppleParty.png 5 | 6 | 7 | ### 一、App 介绍 8 | 9 | AppleParty 是三七互娱旗下37手游 iOS 团队研发,实现快速操作 App Store Connect 后台的自动化 macOS 工具。 10 | 11 | 12 | **使用和原理介绍:** 13 | - [AppleParty(苹果派)v3 支持 App Store 新定价机制 - 批量配置自定价格和销售范围](https://juejin.cn/post/7226327556198744122) 14 | - [开源一款苹果 macOS 工具 - AppleParty(苹果派)](https://juejin.cn/post/7081069026515877919) 15 | - [使用 App Store Connect API 批量创建内购商品 ](https://juejin.cn/post/7181099247956131896) 16 | 17 | **支持功能** 18 | 19 | - 内购买项目管理(批量创建和更新); 20 | - ~~批量商店图和预览视频上传和更新~~(苹果2023年禁止使用 XML feed 上传,ASC API 上传方式敬请期待~); 21 | - 邮件发送工具; 22 | - 二维码扫描和生成工具; 23 | - 上传 iap 文件; 24 | 25 | 26 | **TODO** 27 | 28 | - 数据报表下载; 29 | - 元数据管理; 30 | - 开发证书管理; 31 | - 更多功能,敬请期待~ 32 | 33 | 34 | ### 二、项目背景 35 | 36 | 目前,iOS/macOS App 上架 App Store,与苹果打交道的唯一方式,就是登陆苹果 App Store Connect 后台([https://appstoreconnect.apple.com](https://appstoreconnect.apple.com),通过苹果后台进行 App 所有的信息和素材等送审准备工作。但是,目前苹果后台的自动化水平还处于零基础,很多重复的操作和功能,都没有提供批量处理方案,比如: 37 | 38 | - 商店截图和预览视频的上传 39 | - 应用内购商品的创建和更新 40 | - App 本地化的元数据信息配置 41 | - ... 42 | 43 | 44 | App 分析的指标: 45 | 46 | - 展示次数 47 | - 产品页面查看次数 48 | - 首次下载次数 49 | - 净预订量 50 | - 平台版本(iOS14.5、iOS15...) 51 | - 页面类型(产品页面、商店表单、App内活动...) 52 | - 用户来源(网页引荐来源、App 引荐来源、AppStore 浏览、AppStore 搜索、活动通知...) 53 | - ... 54 | 55 | 以上的 App 分析数据,每次只能下载一个指标的数据,每个 App 有十几个指标,操作这些重复的配置往往占用了运营同学非常长的时间,效率低且重复无聊的工作,导致我们长期无法做更多的时间开启和享受创造性。 56 | 57 | 基于以上种种痛点,我们从多个技术手段,打造了 Apple Party(苹果派对)工具! 通过尽可能快速实现操作的自动化流程,从而大大提高苹果后台的操作效率! 58 | 59 | 60 | 61 | **Apple Party(苹果派)** 62 | 63 | 我们倡导工作之余,丰富多彩的生活要领,健身、旅游、聚会、培养艺术兴趣等等。 64 | 65 | - Party:派对 即 “宴会,聚会” 的意思,大家聚在一起庆祝和休闲的一种活动。 66 | 67 | 所以,Apple Party(苹果派对),简称:**苹果派**,就是希望大家在使用苹果的服务时,像似参加一场苹果派对,尽情欢乐,欢聚宴会~ 68 | 69 | 70 | ### 三、安装说明 71 | 72 | #### 下载安装包 73 | 74 | - [Releases](https://github.com/37iOS/AppleParty/releases) 75 | 76 | 77 | **update 更新** 78 | 79 | 项目使用 Sparkle 来更新,重启就会自动检查更新。也可以手动检查更新。 80 | 81 | 82 | #### 手动构建 83 | **build 构建** 84 | 85 | 在项目主目录下,执行安装依赖库命令: 86 | ``` 87 | pod install 88 | ``` 89 | 90 | 然后双击 `AppleParty.xcworkspace` 打开项目构建。 91 | 92 | 93 | > 项目依赖 Swift Package,Xcode 可能下载失败,可以清理缓存试试: 打开 Xcode File -> Packages -> Reset Package Caches 94 | 95 | ### 四、FAQ 96 | 97 | - [AppleParty 使用说明 - wiki](https://github.com/37iOS/AppleParty/wiki/AppleParty-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E) 98 | 99 | - [New Issue](https://github.com/37iOS/AppleParty/issues/new/choose) 100 | 101 | 102 | ### 五、效果示例 103 | 104 | screenshot/01.png 105 | screenshot/02.png 106 | screenshot/03.png 107 | screenshot/04.png 108 | screenshot/05.png 109 | screenshot/06.png 110 | screenshot/07.png 111 | screenshot/08.png 112 | screenshot/09.png 113 | screenshot/10.png 114 | 115 | 116 | 117 | ### 六、特别感谢 118 | 119 | - [Alamofire/Alamofire](https://github.com/Alamofire/Alamofire) 120 | - [Kitura/Swift-SMTP](https://github.com/Kitura/Swift-SMTP) 121 | - [SnapKit/SnapKit](https://github.com/SnapKit/SnapKit) 122 | - [sparkle-project/Sparkle](https://github.com/sparkle-project/Sparkle) 123 | - [tid-kijyun/Kanna](https://github.com/tid-kijyun/Kanna) 124 | - [drmohundro/SWXMLHash](https://github.com/drmohundro/SWXMLHash) 125 | - [jdg/MBProgressHUD](https://github.com/jdg/MBProgressHUD) 126 | - [joshuajylin/MBProgressHUD-macOS](https://github.com/joshuajylin/MBProgressHUD-macOS) 127 | - [Yueoaix/SymbolicatorX](https://github.com/Yueoaix/SymbolicatorX) 128 | - [fpotter/ExpandingDatePicker](https://github.com/fpotter/ExpandingDatePicker) 129 | - [kishikawakatsumi/KeychainAccess](https://github.com/kishikawakatsumi/KeychainAccess) 130 | - [AvdLee/appstoreconnect-swift-sdk](https://github.com/AvdLee/appstoreconnect-swift-sdk) 131 | 132 | 133 | -------------------------------------------------------------------------------- /AppleParty/Shared/Info/InfoCenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoCenter.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/17. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let InfoCenterKey_Session_Key = "InfoCenterKey_Session_Key" 12 | let InfoCenterKey_TrusDevice_Key = "InfoCenterKey_TrusDevice_Key" 13 | 14 | /// AppStoreConnect 密钥模型 15 | struct AppStoreConnectKey: Codable { 16 | var aliasName: String 17 | var issuerID: String 18 | var privateKeyID: String 19 | var privateKey: String 20 | var isused: Bool 21 | 22 | mutating func model(_ asck: AppStoreConnectKey, _ isused: Bool) -> AppStoreConnectKey { 23 | AppStoreConnectKey(aliasName: asck.aliasName, issuerID: asck.issuerID, privateKeyID: asck.privateKeyID, privateKey: asck.privateKey, isused: isused) 24 | } 25 | } 26 | 27 | 28 | /// 信息模型 29 | struct AppleInfoSession: Codable { 30 | var scnt: String 31 | var sessionId: String 32 | var cookies: Data 33 | var ascKeys: [AppStoreConnectKey] 34 | } 35 | 36 | 37 | struct InfoCenter { 38 | static var shared = InfoCenter() 39 | 40 | var session: AppleInfoSession { 41 | set { 42 | let encoder = JSONEncoder() 43 | if let data = try? encoder.encode(newValue) { 44 | #if DEBUG 45 | UserDefaults.standard.set(data, forKey: InfoCenterKey_Session_Key) 46 | #else 47 | try? APUtil.keychain.set(data, key: InfoCenterKey_Session_Key) 48 | #endif 49 | } 50 | } 51 | get { 52 | var data: Data? 53 | #if DEBUG 54 | data = UserDefaults.standard.data(forKey: InfoCenterKey_Session_Key) 55 | #else 56 | data = try? APUtil.keychain.getData(InfoCenterKey_Session_Key) 57 | #endif 58 | if let data = data, let model = try? JSONDecoder().decode(AppleInfoSession.self, from: data) { 59 | return model 60 | } else { 61 | return AppleInfoSession(scnt: "", sessionId: "", cookies: Data(), ascKeys: []) 62 | } 63 | } 64 | } 65 | 66 | var scnt: String { 67 | get { 68 | session.scnt 69 | } 70 | set { 71 | session = AppleInfoSession(scnt: newValue, sessionId: session.sessionId, cookies: session.cookies, ascKeys: session.ascKeys) 72 | } 73 | } 74 | 75 | var sessionId: String { 76 | get { 77 | session.sessionId 78 | } 79 | set { 80 | session = AppleInfoSession(scnt: session.scnt, sessionId: newValue, cookies: session.cookies, ascKeys: session.ascKeys) 81 | } 82 | } 83 | 84 | var cookies: [HTTPCookie] { 85 | get { 86 | let data = session.cookies 87 | // NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, HTTPCookie.self], from: data) 88 | // adopt NSSecureCoding. Class 'NSHTTPCookie' does not adopt it 89 | let cookies = try? NSKeyedUnarchiver.unarchiveObject(with: data) 90 | return cookies as? [HTTPCookie] ?? [] 91 | } 92 | set { 93 | let cookieData = (try? NSKeyedArchiver.archivedData(withRootObject: newValue, requiringSecureCoding: false)) ?? Data() 94 | session = AppleInfoSession(scnt: session.scnt, sessionId: session.sessionId, cookies: cookieData, ascKeys: session.ascKeys) 95 | } 96 | } 97 | 98 | var ascKeys: [AppStoreConnectKey] { 99 | get { 100 | session.ascKeys 101 | } 102 | set { 103 | session = AppleInfoSession(scnt: session.scnt, sessionId: session.sessionId, cookies: session.cookies, ascKeys: newValue) 104 | } 105 | } 106 | 107 | var currentASCKey: AppStoreConnectKey? { 108 | get { 109 | let accounts = self.ascKeys 110 | let models = accounts.filter({ $0.isused == true }) 111 | return models.first 112 | } 113 | } 114 | 115 | var trusDevice: Bool { 116 | get { 117 | if let trus = APUtil.defaults.bool(forKey: InfoCenterKey_TrusDevice_Key) { 118 | return trus 119 | } 120 | return true //默认为信任设备 121 | } 122 | set { 123 | APUtil.defaults.set(newValue, forKey: InfoCenterKey_TrusDevice_Key) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /AppleParty/Shared/UI/APDebugVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APDebugVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/21. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APDebugVC: NSViewController { 12 | 13 | @IBOutlet var debugTextView: NSTextView! 14 | @IBOutlet weak var refreshBtn: NSButton! 15 | @IBOutlet weak var shareBtn: NSButton! 16 | 17 | var debugLog: String = "" { 18 | didSet { 19 | uploadData() 20 | } 21 | } 22 | 23 | var fileURL: URL? { 24 | didSet { 25 | refreshBtn.isHidden = false 26 | reloadFileLogs() 27 | } 28 | } 29 | 30 | // Menu 31 | private lazy var editMenu: NSMenu = { 32 | let menu = NSMenu() 33 | let editMenuItems = [ 34 | NSMenuItem(title: "邮件发送", action: #selector(emailShare), keyEquivalent: ""), 35 | NSMenuItem(title: "隔空投送", action: #selector(airDropShare), keyEquivalent: ""), 36 | NSMenuItem(title: "其它方式", action: #selector(otherShare), keyEquivalent: ""), 37 | ] 38 | for editMenuItem in editMenuItems { 39 | menu.addItem(editMenuItem) 40 | } 41 | return menu 42 | }() 43 | 44 | override func viewDidLoad() { 45 | super.viewDidLoad() 46 | } 47 | 48 | @IBAction func clickedRefreshBtn(_ sender: Any) { 49 | reloadFileLogs() 50 | } 51 | 52 | @IBAction func clickedShareBtn(_ sender: NSButton) { 53 | let p = NSPoint(x: sender.frame.width, y: 0) //按钮右边 54 | self.editMenu.popUp(positioning: nil, at: p, in: sender) 55 | } 56 | 57 | } 58 | 59 | 60 | extension APDebugVC { 61 | 62 | func uploadData() { 63 | DispatchQueue.main.async { 64 | self.debugTextView.string = self.debugLog 65 | self.debugTextView.scrollRangeToVisible(NSMakeRange(self.debugTextView.string.count, 0)) 66 | } 67 | } 68 | 69 | 70 | func reloadFileLogs() { 71 | guard let file = fileURL, let logs = try? String(contentsOf: file, encoding: .utf8) else { 72 | debugTextView.string = "读取日志失败!\(String(describing: fileURL?.path))" 73 | return 74 | } 75 | debugLog = logs 76 | } 77 | 78 | func getTextFileURL(text: String) -> URL? { 79 | 80 | guard let data = text.data(using: .utf8) else { return nil } 81 | 82 | let dateFormatter = DateFormatter() 83 | dateFormatter.dateFormat = "yyyyMMdd_HHmm" 84 | let date = dateFormatter.string(from: Date()) 85 | let filename = "AppleParty-Logs_\(date).txt" 86 | 87 | let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()) 88 | .appendingPathComponent(filename) 89 | 90 | do { 91 | try data.write(to: tempURL, options: .atomic) 92 | 93 | return tempURL 94 | } catch { 95 | assertionFailure("Failed to write temporary URL for pasteboard: \(String(describing: error))") 96 | return nil 97 | } 98 | } 99 | } 100 | 101 | 102 | extension APDebugVC { 103 | 104 | @objc func emailShare() { 105 | let text = debugTextView.string 106 | let mainStoryBoard = NSStoryboard(name: "EmailTool", bundle: nil) 107 | let windowController = mainStoryBoard.instantiateController(withIdentifier: "EmailTool") as! NSWindowController 108 | let controller = windowController.contentViewController as! EmailToolVC 109 | controller.emailTitle = "苹果派-错误日志" 110 | controller.emailContent = text 111 | controller.attachmentFileUrl = getTextFileURL(text: text) 112 | windowController.showWindow(self) 113 | } 114 | 115 | @objc func airDropShare() { 116 | let text = debugTextView.string 117 | guard let url = getTextFileURL(text: text) else { 118 | otherShare() 119 | return 120 | } 121 | 122 | let service = NSSharingService(named: .sendViaAirDrop)! 123 | let items: [NSURL] = [url as NSURL] 124 | if service.canPerform(withItems: items) { 125 | service.perform(withItems: items) 126 | } else { 127 | NSAlert.show("Cannot perform AirDrop!") 128 | } 129 | } 130 | 131 | @objc func otherShare() { 132 | var item: Any = debugTextView.string 133 | if let text = item as? String, let url = getTextFileURL(text: text) { 134 | item = url 135 | } 136 | let picker = NSSharingServicePicker(items: [item]) 137 | picker.show(relativeTo: .zero, of: shareBtn, preferredEdge: .maxX) 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /AppleParty/LoginView/AppleWebLogin/AppleWebLoginCore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleWebLoginCore.swift 3 | // AppleWebLogin 4 | // 5 | // Created by 秋星桥 on 2024/10/23. 6 | // ref: https://github.com/Lakr233/AppleWebLogin 7 | 8 | import Combine 9 | @preconcurrency import WebKit 10 | 11 | //private let loginURL = URL(string: "https://account.apple.com/sign-in")! 12 | private let loginURL = URL(string: "https://appstoreconnect.apple.com/login")! 13 | 14 | public class AppleWebLoginCore: NSObject, WKUIDelegate, WKNavigationDelegate { 15 | var webView: WKWebView { 16 | associatedWebView 17 | } 18 | 19 | private let associatedWebView: WKWebView 20 | private var dataPopulationTimer: Timer? = nil 21 | private var firstLoadComplete = false 22 | 23 | public private(set) var onFirstLoadComplete: (() -> Void)? 24 | public var onCredentialPopulation: ((String, [HTTPCookie]) -> Void)? 25 | 26 | override public init() { 27 | let contentController = WKUserContentController() 28 | let configuration = WKWebViewConfiguration() 29 | configuration.defaultWebpagePreferences.allowsContentJavaScript = true 30 | configuration.userContentController = contentController 31 | configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs") 32 | configuration.websiteDataStore = .nonPersistent() 33 | 34 | associatedWebView = .init( 35 | frame: CGRect(x: 0, y: 0, width: 1920, height: 1080), 36 | configuration: configuration 37 | ) 38 | associatedWebView.isHidden = true 39 | 40 | super.init() 41 | 42 | associatedWebView.uiDelegate = self 43 | associatedWebView.navigationDelegate = self 44 | 45 | associatedWebView.load(.init(url: loginURL)) 46 | 47 | #if DEBUG 48 | if associatedWebView.responds(to: Selector(("setInspectable:"))) { 49 | associatedWebView.perform(Selector(("setInspectable:")), with: true) 50 | } 51 | #endif 52 | 53 | let dataPopulationTimer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in 54 | guard let self else { return } 55 | removeUnwantedElements() 56 | populateData() 57 | } 58 | RunLoop.main.add(dataPopulationTimer, forMode: .common) 59 | self.dataPopulationTimer = dataPopulationTimer 60 | } 61 | 62 | deinit { 63 | dataPopulationTimer?.invalidate() 64 | onCredentialPopulation = nil 65 | } 66 | 67 | public func webView(_: WKWebView, didFinish _: WKNavigation!) { 68 | guard !firstLoadComplete else { return } 69 | defer { firstLoadComplete = true } 70 | associatedWebView.isHidden = false 71 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 72 | self.onFirstLoadComplete?() 73 | self.onFirstLoadComplete = nil 74 | } 75 | } 76 | 77 | // public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) { 78 | // let request = navigationAction.request 79 | // if let headers = request.allHTTPHeaderFields { 80 | // print(request.url?.absoluteString) 81 | // print("headers: \(headers)") 82 | // if let scntValue = headers["scnt"] { 83 | // print("scnt value: \(scntValue)") 84 | // } 85 | // } 86 | // decisionHandler(.allow, preferences) 87 | // } 88 | 89 | public func installFirstLoadCompleteTrap(_ block: @escaping () -> Void) { 90 | onFirstLoadComplete = block 91 | } 92 | 93 | public func installCredentialPopulationTrap(_ block: @escaping (String, [HTTPCookie]) -> Void) { 94 | onCredentialPopulation = block 95 | } 96 | 97 | private func removeUnwantedElements() { 98 | let removeElements = """ 99 | Element.prototype.remove = function() { 100 | this.parentElement.removeChild(this); 101 | } 102 | NodeList.prototype.remove = HTMLCollection.prototype.remove = function() { 103 | for(var i = this.length - 1; i >= 0; i--) { 104 | if(this[i] && this[i].parentElement) { 105 | this[i].parentElement.removeChild(this[i]); 106 | } 107 | } 108 | } 109 | document.getElementById("header").remove(); 110 | document.getElementsByClassName('landing__animation').remove(); 111 | """ 112 | associatedWebView.evaluateJavaScript(removeElements) { _, _ in 113 | } 114 | } 115 | 116 | private func populateData() { 117 | guard let onCredentialPopulation else { return } 118 | associatedWebView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in 119 | //print(cookies) 120 | for cookie in cookies where cookie.name == "myacinfo" { 121 | let value = cookie.value 122 | onCredentialPopulation(value, cookies) 123 | self.onCredentialPopulation = nil 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /AppleParty/LoginView/APWebLoginVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APWebLoginVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2024/10/29. 6 | // Copyright © 2024 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APWebLoginVC: NSViewController { 12 | 13 | public var cancelHandle: (() -> Void)? 14 | public var successHandle: (() -> Void)? 15 | private var webCore: AppleWebLoginCore? = nil 16 | 17 | @IBOutlet weak var loginBtn: NSButton! 18 | @IBOutlet weak var cancelBtn: NSButton! 19 | @IBOutlet weak var indicatorView: NSProgressIndicator! 20 | @IBOutlet weak var tipsWarningView: NSTextField! 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | // Do view setup here. 25 | } 26 | 27 | @IBAction func clickedCancelBtn(_ sender: NSButton) { 28 | closeView() 29 | cancelHandle?() 30 | } 31 | 32 | @IBAction func clickedLoginBtn(_ sender: NSButton) { 33 | validateSession() 34 | } 35 | 36 | // 判断是登陆态是否过期 37 | func validateSession() { 38 | viewEnabled(false) 39 | APClient.signInSession.request { [weak self] result, response, error in 40 | self?.viewEnabled(true) 41 | let code = response?.statusCode 42 | switch code { 43 | case 200, 201: 44 | UserCenter.shared.isAuthorized = true 45 | self?.successHandle?() 46 | self?.closeView() 47 | default: 48 | let errors = dictionaryArray(result["serviceErrors"]) 49 | let msg = string(from: errors.first?["message"]) 50 | self?.showTips(msg.isEmpty ? error.debugDescription : msg) 51 | // 隐藏按钮透视显示 52 | self?.cancelBtn.isEnabled = false 53 | self?.loginBtn.isEnabled = false 54 | self?.loginWithWeb() 55 | } 56 | } 57 | } 58 | 59 | func loginWithWeb() { 60 | let appleWebLoginCore = AppleWebLoginCore() 61 | // 将 webView 添加到视图层次结构中 62 | self.view.addSubview(appleWebLoginCore.webView) 63 | 64 | let closeButton = NSButton(title: "取消", target: self, action: #selector(closeButtonClicked)) 65 | closeButton.attributedTitle = NSAttributedString(string: "取消", attributes: [NSAttributedString.Key.foregroundColor: NSColor.gray]) 66 | closeButton.keyEquivalent = "\u{1B}" // `esc` 快捷键 67 | appleWebLoginCore.webView.addSubview(closeButton) 68 | 69 | // 设置 webView 的约束以适应视图 70 | appleWebLoginCore.webView.translatesAutoresizingMaskIntoConstraints = false 71 | closeButton.translatesAutoresizingMaskIntoConstraints = false 72 | NSLayoutConstraint.activate([ 73 | appleWebLoginCore.webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), 74 | appleWebLoginCore.webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), 75 | appleWebLoginCore.webView.topAnchor.constraint(equalTo: self.view.topAnchor), 76 | appleWebLoginCore.webView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), 77 | 78 | closeButton.trailingAnchor.constraint(equalTo: appleWebLoginCore.webView.trailingAnchor, constant: -10), 79 | closeButton.topAnchor.constraint(equalTo: appleWebLoginCore.webView.topAnchor, constant: 10) 80 | ]) 81 | 82 | // 关闭按钮 83 | 84 | 85 | appleWebLoginCore.installFirstLoadCompleteTrap { 86 | // 处理首次加载完成的逻辑 87 | print("First load complete") 88 | } 89 | 90 | appleWebLoginCore.installCredentialPopulationTrap { token, cookies in 91 | // 处理凭据填充的逻辑 92 | print("Received cookies: \(cookies)") 93 | print("Received token: \(token)") 94 | 95 | if let cks = APClientSession.shared.config.httpCookieStorage?.cookies { 96 | for ck in cks { 97 | APClientSession.shared.config.httpCookieStorage?.deleteCookie(ck) 98 | } 99 | } 100 | for cookie in cookies { 101 | APClientSession.shared.config.httpCookieStorage?.setCookie(cookie) 102 | } 103 | // APClientSession.shared.config.headers.update(name: "Cookie", value: "myacinfo=\(token);") 104 | self.validateSession() 105 | } 106 | self.webCore = appleWebLoginCore 107 | } 108 | 109 | @objc func closeButtonClicked() { 110 | // 处理关闭按钮的点击事件 111 | print("关闭按钮被点击") 112 | closeView() 113 | } 114 | 115 | func viewEnabled(_ isEnabled: Bool) { 116 | showTips("") 117 | loginBtn.isEnabled = isEnabled 118 | isEnabled ? indicatorView.stopAnimation(nil) : indicatorView.startAnimation(nil) 119 | } 120 | 121 | func showTips(_ text: String) { 122 | if text.isEmpty { 123 | tipsWarningView.isHidden = true 124 | tipsWarningView.stringValue = "" 125 | } else { 126 | tipsWarningView.stringValue = text 127 | tipsWarningView.isHidden = false 128 | } 129 | } 130 | 131 | func closeView() { 132 | guard let window = view.window, let parent = window.sheetParent 133 | else { return } 134 | parent.endSheet(window) 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /AppleParty/RootView/APRootWC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APRootWC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/11. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APRootWC: NSWindowController { 12 | 13 | override func windowDidLoad() { 14 | super.windowDidLoad() 15 | setupUI() 16 | } 17 | 18 | func setupUI() { 19 | self.window?.title = "AppleParty" 20 | if #available(macOS 11.0, *) { 21 | self.window?.subtitle = "37 Mobile Games" 22 | } 23 | } 24 | 25 | @IBAction func clickedAccountItem(_ sender: NSToolbarItem?) { 26 | // 登陆时,显示切换账号 27 | if UserCenter.shared.isAuthorized { 28 | if let providers = UserCenter.shared.accountProviders["availableProviders"] as? [[String: Any]] { 29 | var accountList: [Provider] = [] 30 | providers.forEach { provider in 31 | accountList.append(Provider(name: provider["name"] as! String, providerId: String(provider["providerId"] as! Int), publicProviderId: provider["publicProviderId"] as! String)) 32 | } 33 | 34 | accountList.append(Provider(name: "账号登出", providerId: "", publicProviderId: "")) 35 | 36 | let listVC = APSwichAccountPopover() 37 | listVC.accounts = accountList 38 | listVC.selectHandle = { [weak self] row in 39 | guard row >= 0, row < accountList.count else { 40 | return 41 | } 42 | let publicProviderId = accountList[row].publicProviderId 43 | self?.switchAccount(publicProviderId) 44 | } 45 | let pannel = NSPanel(contentViewController: listVC) 46 | pannel.setFrame(NSRect(origin: .zero, size: NSSize(width: 300, height: 350)), display: true) 47 | window?.beginSheet(pannel, completionHandler: nil) 48 | } else { 49 | APHUD.hide(message:"获取登陆账号信息异常~") 50 | } 51 | 52 | } else { 53 | let vc = APWebLoginVC() 54 | vc.successHandle = { [weak self] in 55 | self?.fetchAccountTeamInfo() 56 | self?.window?.title = UserCenter.shared.developerName 57 | if #available(macOS 11.0, *) { 58 | self?.window?.subtitle = UserCenter.shared.accountEmail 59 | } 60 | } 61 | let pannel = NSPanel(contentViewController: vc) 62 | pannel.setFrame(NSRect(origin: .zero, size: NSSize(width: 550, height: 450)), display: true) 63 | window?.beginSheet(pannel, completionHandler: nil) 64 | } 65 | } 66 | 67 | @IBAction func clickedSettingsItem(_ sender: Any) { 68 | let vc = APSettingVC() 69 | let window = NSWindow(contentViewController: vc) 70 | let wc = NSWindowController(window: window) 71 | wc.showWindow(self) 72 | vc.isLoginViewShow = !UserCenter.shared.isAuthorized 73 | } 74 | 75 | @IBAction func clickedGithubItem(_ sender: Any) { 76 | let url = URL(string: kApplePartyGitHub) 77 | NSWorkspace.shared.open(url!) 78 | } 79 | 80 | @IBAction func clicedFeedbackItem(_ sender: Any) { 81 | let url = URL(string: kApplePartyNewIssues) 82 | NSWorkspace.shared.open(url!) 83 | } 84 | 85 | @IBAction func cliced37MobileGamesItem(_ sender: Any) { 86 | let url = URL(string: k37MobileGamesSite) 87 | NSWorkspace.shared.open(url!) 88 | } 89 | 90 | 91 | @IBAction func cliced37iOSTeamItem(_ sender: Any) { 92 | let url = URL(string: k37iOSTeamJueJinSite) 93 | NSWorkspace.shared.open(url!) 94 | } 95 | } 96 | 97 | 98 | extension APRootWC { 99 | 100 | func switchAccount(_ publicProviderId: String) { 101 | guard publicProviderId.count > 0 else { 102 | UserCenter.shared.isAutoLogin = false 103 | UserCenter.shared.isAuthorized = false 104 | // 清掉缓存 105 | //HTTPCookieStorage.shared.cookies?.forEach(HTTPCookieStorage.shared.deleteCookie) 106 | InfoCenter.shared.cookies = [] 107 | setupUI() 108 | return 109 | } 110 | 111 | APClient.switchProvider(publicProviderId: publicProviderId).request(showLoading: true) { [weak self] result, response, error in 112 | guard let err = error else { 113 | self?.validateSession() 114 | return 115 | } 116 | APHUD.hide(message: err.localizedDescription) 117 | } 118 | } 119 | 120 | func validateSession() { 121 | APClient.signInSession.request(showLoading: true) { [weak self] result, response, error in 122 | guard let err = error else { 123 | UserCenter.shared.isAuthorized = true 124 | self?.window?.title = UserCenter.shared.developerName 125 | if #available(macOS 11.0, *) { 126 | self?.window?.subtitle = UserCenter.shared.accountEmail 127 | } 128 | self?.fetchAccountTeamInfo() 129 | return 130 | } 131 | APHUD.hide(message: err.localizedDescription) 132 | } 133 | } 134 | 135 | func fetchAccountTeamInfo() { 136 | // 获取开发者 Team id 信息 137 | APClient.ascProvider.request(completionHandler: nil) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /AppleParty/AppListView/InAppPurchseView/APInAppPurchseCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APInAppPurchseCell.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/28. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APInAppPurchseCell: NSTableCellView { 12 | 13 | 14 | } 15 | 16 | class ImageViewCell: NSTableCellView { 17 | 18 | @IBOutlet weak var imgSel: NSImageView! 19 | 20 | override func awakeFromNib() { 21 | super.awakeFromNib() 22 | } 23 | } 24 | 25 | class UploadCell: NSTableCellView { 26 | 27 | var row: Int = 0 28 | 29 | @IBOutlet weak var imgSel: NSImageView! 30 | @IBOutlet weak var dragView: DragView! 31 | @IBOutlet weak var dragBox: NSView! 32 | 33 | typealias CallBackFunc = (_ path: String, _ row: Int) -> Void 34 | var callBackFunc: CallBackFunc? 35 | 36 | override func awakeFromNib() { 37 | super.awakeFromNib() 38 | dragView.delegate = self 39 | } 40 | } 41 | 42 | extension UploadCell: DragViewDelegate { 43 | func dragView(_ path: String?) { 44 | if let path = path { 45 | debugPrint(path) 46 | imgSel.image = NSImage(contentsOfFile: path) 47 | if let callBackFunc = callBackFunc { 48 | callBackFunc(path, row) 49 | } 50 | } else { 51 | imgSel.image = nil 52 | } 53 | } 54 | } 55 | 56 | 57 | enum ColumnIdetifier: String { 58 | case id 59 | case productID 60 | case productName 61 | case priceLevel 62 | case appleid 63 | case price 64 | case type 65 | case state 66 | 67 | // list 68 | case productPds 69 | case level 70 | case status 71 | case screenshot 72 | case language 73 | case upload 74 | case picname 75 | 76 | var columnValue: NSUserInterfaceItemIdentifier { 77 | return NSUserInterfaceItemIdentifier(rawValue: self.rawValue+"Column") 78 | } 79 | var cellValue: NSUserInterfaceItemIdentifier { 80 | return NSUserInterfaceItemIdentifier(rawValue: self.rawValue+"Cell") 81 | } 82 | } 83 | 84 | extension NSUserInterfaceItemIdentifier { 85 | func stringValue() -> String { 86 | switch self { 87 | case ColumnIdetifier.id.columnValue: 88 | return ColumnIdetifier.id.rawValue 89 | case ColumnIdetifier.productID.columnValue: 90 | return ColumnIdetifier.productID.rawValue 91 | case ColumnIdetifier.productName.columnValue: 92 | return ColumnIdetifier.productName.rawValue 93 | case ColumnIdetifier.price.columnValue: 94 | return ColumnIdetifier.price.rawValue 95 | case ColumnIdetifier.type.columnValue: 96 | return ColumnIdetifier.type.rawValue 97 | case ColumnIdetifier.state.columnValue: 98 | return ColumnIdetifier.state.rawValue 99 | case ColumnIdetifier.productPds.columnValue: 100 | return ColumnIdetifier.productPds.rawValue 101 | case ColumnIdetifier.level.columnValue: 102 | return ColumnIdetifier.level.rawValue 103 | case ColumnIdetifier.status.columnValue: 104 | return ColumnIdetifier.status.rawValue 105 | case ColumnIdetifier.appleid.columnValue: 106 | return ColumnIdetifier.appleid.rawValue 107 | case ColumnIdetifier.priceLevel.columnValue: 108 | return ColumnIdetifier.priceLevel.rawValue 109 | case ColumnIdetifier.screenshot.columnValue: 110 | return ColumnIdetifier.screenshot.rawValue 111 | case ColumnIdetifier.picname.columnValue: 112 | return ColumnIdetifier.picname.rawValue 113 | case ColumnIdetifier.upload.columnValue: 114 | return ColumnIdetifier.upload.rawValue 115 | case ColumnIdetifier.language.columnValue: 116 | return ColumnIdetifier.language.rawValue 117 | default: 118 | return "none" 119 | } 120 | } 121 | 122 | func enumValue() -> NSUserInterfaceItemIdentifier { 123 | switch self { 124 | case ColumnIdetifier.id.columnValue: 125 | return ColumnIdetifier.id.cellValue 126 | case ColumnIdetifier.productID.columnValue: 127 | return ColumnIdetifier.productID.cellValue 128 | case ColumnIdetifier.productName.columnValue: 129 | return ColumnIdetifier.productName.cellValue 130 | case ColumnIdetifier.price.columnValue: 131 | return ColumnIdetifier.price.cellValue 132 | case ColumnIdetifier.type.columnValue: 133 | return ColumnIdetifier.type.cellValue 134 | case ColumnIdetifier.state.columnValue: 135 | return ColumnIdetifier.state.cellValue 136 | case ColumnIdetifier.productPds.columnValue: 137 | return ColumnIdetifier.productPds.cellValue 138 | case ColumnIdetifier.level.columnValue: 139 | return ColumnIdetifier.level.cellValue 140 | case ColumnIdetifier.status.columnValue: 141 | return ColumnIdetifier.status.cellValue 142 | case ColumnIdetifier.appleid.columnValue: 143 | return ColumnIdetifier.appleid.cellValue 144 | case ColumnIdetifier.priceLevel.columnValue: 145 | return ColumnIdetifier.priceLevel.cellValue 146 | case ColumnIdetifier.screenshot.columnValue: 147 | return ColumnIdetifier.screenshot.cellValue 148 | case ColumnIdetifier.picname.columnValue: 149 | return ColumnIdetifier.picname.cellValue 150 | case ColumnIdetifier.upload.columnValue: 151 | return ColumnIdetifier.upload.cellValue 152 | case ColumnIdetifier.language.columnValue: 153 | return ColumnIdetifier.language.cellValue 154 | default: 155 | return NSUserInterfaceItemIdentifier(rawValue: "none") 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /AppleParty/IPAUpload/APIPAUploadVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIPAUploadVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/5/12. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APIPAUploadVC: NSViewController { 12 | 13 | @IBOutlet weak var appIdTextView: NSTextField! 14 | @IBOutlet weak var appIdTextField: NSTextField! 15 | @IBOutlet weak var spasswordLbl: NSTextField! 16 | @IBOutlet weak var submitBtn: NSButton! 17 | 18 | //通过外界传入的 apple id时,不需要用户填写 19 | var apple_id: String? { 20 | didSet { 21 | if let appId = apple_id { 22 | appIdTextView.stringValue = appId 23 | appIdTextView.isHidden = false 24 | appIdTextField.isHidden = true 25 | } else { 26 | appIdTextView.stringValue = "" 27 | appIdTextView.isHidden = true 28 | appIdTextField.isHidden = false 29 | } 30 | } 31 | } 32 | 33 | private var ipaFileURL: URL? 34 | private var fileDropZoneView = DropZoneView(fileTypes: [".ipa"], text: "点击或拖拽IPA到这里") 35 | private var uploadModel = XMLModel() 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | setupUI() 40 | updateSPasswordUI() 41 | } 42 | 43 | func setupUI() { 44 | 45 | fileDropZoneView.translatesAutoresizingMaskIntoConstraints = false 46 | fileDropZoneView.delegate = self 47 | view.addSubview(fileDropZoneView) 48 | fileDropZoneView.snp.makeConstraints { (make) in 49 | make.top.equalTo(submitBtn.snp.bottom).offset(15) 50 | make.left.equalToSuperview().offset(20) 51 | make.right.equalToSuperview().offset(-20) 52 | make.bottom.equalToSuperview().offset(-30) 53 | } 54 | } 55 | 56 | func updateSPasswordUI() { 57 | if let sp = UserCenter.shared.currentSPassword { 58 | spasswordLbl.stringValue = "(当前选择:\(sp.account))" 59 | } else { 60 | spasswordLbl.stringValue = "(错误:当前未指定专用密码!)" 61 | } 62 | } 63 | 64 | @IBAction func clickedSPasswordBtn(_ sender: NSButton) { 65 | let vc = APSPasswordSettingVC() 66 | vc.updateCompletion = { [weak self] ps in 67 | self?.updateSPasswordUI() 68 | } 69 | presentAsSheet(vc) 70 | } 71 | 72 | @IBAction func clickedSubmitBtn(_ sender: NSButton) { 73 | uploadIpaFile() 74 | } 75 | 76 | } 77 | 78 | // MARK: - Private Method 79 | extension APIPAUploadVC { 80 | 81 | private func uploadIpaFile() { 82 | 83 | var appId = appIdTextField.stringValue 84 | if let appleId = apple_id { 85 | appId = appleId 86 | } 87 | guard !appId.isEmpty else { 88 | APHUD.hide(message: "请先填写 app id ~", delayTime: 1) 89 | return 90 | } 91 | 92 | guard let sp = UserCenter.shared.currentSPassword else { 93 | let vc = APSPasswordSettingVC() 94 | vc.updateCompletion = { [weak self] spassword in 95 | self?.uploadIpaFile() 96 | } 97 | presentAsSheet(vc) 98 | APHUD.hide(message: "请先设置或指定专用密码~", delayTime: 1) 99 | return 100 | } 101 | 102 | guard let ipaFileURL = ipaFileURL else { 103 | APHUD.hide(message: "请先上传 ipa 文件~", delayTime: 1) 104 | return 105 | } 106 | 107 | APHUD.show(message: "上传中", view: self.view) 108 | 109 | DispatchQueue.global(qos: .userInitiated).async { [self] in 110 | 111 | uploadModel = XMLModel() 112 | uploadModel.apple_id = appId 113 | uploadModel.ipa_size = ipaFileURL.fileSize() 114 | uploadModel.ipa_md5 = ipaFileURL.fileMD5() ?? "" 115 | uploadModel.filePaths = ["ipa.ipa": ipaFileURL.path] 116 | 117 | // 获取创建 itms 文件的路径 118 | let filePath = XMLManager.getIpaPath(appId) 119 | 120 | // 先删除旧的文档 121 | XMLManager.deleteITMS(filePath) 122 | 123 | uploadModel.createIpaFile(directoryPath: filePath) 124 | 125 | let result = XMLManager.uploadITMS(account: sp.account, pwd: sp.password, filePath: filePath) 126 | 127 | DispatchQueue.main.async { 128 | APHUD.hide() 129 | self.closeSelfAndCallBack(result) 130 | } 131 | } 132 | 133 | } 134 | 135 | 136 | func closeSelfAndCallBack(_ result: (Int32, String?)) { 137 | if result.0 == 0 { 138 | NSAlert.show("ipa文件上传成功!稍后可在苹果后台查看~") 139 | }else { 140 | let sb = NSStoryboard(name: "APDebugVC", bundle: Bundle(for: self.classForCoder)) 141 | let newWC = sb.instantiateController(withIdentifier: "APDebugWC") as? NSWindowController 142 | let logVC = newWC?.contentViewController as? APDebugVC 143 | newWC?.window?.title = "ipa上传错误日志" 144 | logVC?.debugLog = result.1 ?? "" 145 | newWC?.showWindow(self) 146 | } 147 | } 148 | } 149 | 150 | 151 | // MARK: - DropZoneViewDelegate 152 | extension APIPAUploadVC: DropZoneViewDelegate { 153 | 154 | func receivedFile(dropZoneView: DropZoneView, fileURL: URL) { 155 | ipaFileURL = fileURL 156 | } 157 | 158 | func receivedMouseDown(dropZoneView: DropZoneView, theEvent: NSEvent) { 159 | let openPanel = NSOpenPanel() 160 | openPanel.canChooseFiles = true 161 | openPanel.canChooseDirectories = false 162 | openPanel.allowsMultipleSelection = false 163 | openPanel.allowedFileTypes = ["ipa"] 164 | 165 | openPanel.beginSheetModal(for: self.view.window!) { (modalResponse) in 166 | if modalResponse == .OK { 167 | if let fileURL = openPanel.url { 168 | self.ipaFileURL = fileURL 169 | dropZoneView.setFile(fileURL) 170 | } 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /AppleParty/Shared/UI/APSPasswordSettingVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APSPasswordSettingVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/5/18. 6 | // Copyright © 2022 37 Mobile models. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APSPasswordSettingVC: NSViewController { 12 | 13 | // 模型 14 | var models = [SPassword]() 15 | // 回调当前选择的账号 16 | var updateCompletion: ((_ model: SPassword?) -> Void)? 17 | 18 | @IBOutlet weak var tableView: NSTableView! 19 | 20 | 21 | @IBAction func clickedAddBtn(_ sender: Any) { 22 | let vc = APSPasswordEditVC() 23 | vc.titleString = "新增专用密码" 24 | vc.updateCompletion = { [weak self] news in 25 | // 相同账号的只保留最新 26 | self?.models = self?.models.filter({ $0.account != news.account }) ?? [] 27 | if news.isused { 28 | // 只能有一个是使用的账号,其它为否 29 | self?.models = self?.models.map({ sp in 30 | var spp = sp 31 | return spp.model(sp, false) 32 | }) ?? [] 33 | } 34 | self?.models.append(news) 35 | self?.tableView.reloadData() 36 | } 37 | presentAsSheet(vc) 38 | } 39 | 40 | @IBAction func clickedSaveBtn(_ sender: Any) { 41 | 42 | guard models.isNotEmpty else { 43 | APHUD.hide(message: "账号邮箱和专用密码不能为空!", view: view, delayTime: 2) 44 | return 45 | } 46 | 47 | // 保存数据 48 | UserCenter.shared.secondaryPasswordList = models 49 | 50 | // 回调当前选择的账号 51 | if let block = updateCompletion { 52 | let models = self.models.filter({ $0.isused == true }) 53 | block(models.first) 54 | } 55 | dismiss(self) 56 | } 57 | 58 | @IBAction func clickedCancelBtn(_ sender: Any) { 59 | dismiss(self) 60 | } 61 | 62 | private lazy var editMenu: NSMenu = { 63 | let menu = NSMenu() 64 | let saveItem = NSMenuItem() 65 | saveItem.title = "修改" 66 | saveItem.target = self 67 | saveItem.action = #selector(tableViewEditItemClicked) 68 | menu.addItem(saveItem) 69 | let removeItem = NSMenuItem() 70 | removeItem.title = "删除" 71 | removeItem.target = self 72 | removeItem.action = #selector(tableViewDeleteItemClicked) 73 | menu.addItem(removeItem) 74 | return menu 75 | }() 76 | 77 | @objc private func tableViewEditItemClicked(_ sender: AnyObject) { 78 | let row = tableView.clickedRow 79 | guard row >= 0 else { return } 80 | 81 | let result = models 82 | let index = result.index(result.startIndex, offsetBy: row) 83 | let model = result[index] 84 | let vc = APSPasswordEditVC() 85 | vc.titleString = "新增专用密码" 86 | vc.spassword = model 87 | vc.updateCompletion = { [weak self] news in 88 | if news.isused { 89 | // 只能有一个是使用的账号,其它为否 90 | self?.models = self?.models.map({ sp in 91 | var spp = sp 92 | return spp.model(sp, false) 93 | }) ?? [] 94 | } 95 | self?.models[index] = news 96 | self?.tableView.reloadData() 97 | } 98 | presentAsSheet(vc) 99 | } 100 | 101 | @objc private func tableViewDeleteItemClicked(_ sender: AnyObject) { 102 | let row = tableView.clickedRow 103 | guard row >= 0 else { return } 104 | 105 | let result = models 106 | let index = result.index(result.startIndex, offsetBy: row) 107 | models.remove(at: index) 108 | tableView.reloadData() 109 | } 110 | 111 | override func viewDidLoad() { 112 | super.viewDidLoad() 113 | setupUI() 114 | } 115 | 116 | func setupUI() { 117 | 118 | tableView.menu = editMenu 119 | tableView.delegate = self 120 | tableView.dataSource = self 121 | 122 | models = UserCenter.shared.secondaryPasswordList 123 | tableView.reloadData() 124 | } 125 | } 126 | 127 | 128 | // MARK: NSTableViewDataSource && NSTableViewDelegate 129 | extension APSPasswordSettingVC: NSTableViewDataSource, NSTableViewDelegate { 130 | func numberOfRows(in tableView: NSTableView) -> Int { 131 | return models.count 132 | } 133 | 134 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 135 | return 30.0 136 | } 137 | 138 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 139 | let result = models 140 | let index = result.index(result.startIndex, offsetBy: row) 141 | let model = result[index] 142 | let identifier = tableColumn!.identifier 143 | let identifierString = identifier.rawValue 144 | 145 | if identifierString == "AccountCell" { 146 | let cellView = tableView.makeView(withIdentifier: identifier, owner: self) as! NSTableCellView 147 | cellView.textField!.stringValue = model.account 148 | return cellView 149 | } 150 | else if identifierString == "PasswordCell" { 151 | let cellView = tableView.makeView(withIdentifier: identifier, owner: self) as! NSTableCellView 152 | cellView.textField!.stringValue = model.password 153 | return cellView 154 | } 155 | else if identifierString == "currentUseCell" { 156 | let cellView = tableView.makeView(withIdentifier: identifier, owner: self) as! NSTableCellView 157 | cellView.textField!.stringValue = model.isused ? "✓" : "-" 158 | return cellView 159 | } 160 | else { 161 | print("unhandled colum id: \(identifierString)") 162 | } 163 | return nil 164 | } 165 | 166 | 167 | // MARK: 是否可以选中单元格 168 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { 169 | 170 | return true 171 | } 172 | 173 | func tableViewSelectionDidChange(_ notification: Notification) { 174 | let table = notification.object as! NSTableView 175 | table.deselectRow(table.selectedRow) 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /AppleParty/AppListView/APAppListCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 50 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /AppleParty/Shared/UI/APASCKeysSettingVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APASCKeysSettingVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/11/18. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APASCKeysSettingVC: NSViewController { 12 | 13 | // 模型 14 | var models = [AppStoreConnectKey]() 15 | // 回调当前选择的账号 16 | var updateCompletion: ((_ model: AppStoreConnectKey?) -> Void)? 17 | 18 | @IBOutlet weak var tableView: NSTableView! 19 | 20 | 21 | @IBAction func clickedAddBtn(_ sender: Any) { 22 | let vc = APASCKeysEditVC() 23 | vc.titleString = "新增 API 密钥" 24 | vc.updateCompletion = { [weak self] news in 25 | // 相同账号的只保留最新 26 | self?.models = self?.models.filter({ $0.aliasName != news.aliasName }) ?? [] 27 | if news.isused { 28 | // 只能有一个是使用的账号,其它为否 29 | self?.models = self?.models.map({ sp in 30 | var spp = sp 31 | return spp.model(sp, false) 32 | }) ?? [] 33 | } 34 | self?.models.append(news) 35 | self?.tableView.reloadData() 36 | } 37 | presentAsSheet(vc) 38 | } 39 | 40 | @IBAction func clickedSaveBtn(_ sender: Any) { 41 | 42 | guard models.isNotEmpty else { 43 | APHUD.hide(message: "密钥配置不能为空!", view: view, delayTime: 2) 44 | return 45 | } 46 | 47 | // 保存数据 48 | InfoCenter.shared.ascKeys = models 49 | 50 | // 回调当前选择的账号 51 | if let block = updateCompletion { 52 | let models = self.models.filter({ $0.isused == true }) 53 | block(models.first) 54 | } 55 | dismiss(self) 56 | } 57 | 58 | @IBAction func clickedCancelBtn(_ sender: Any) { 59 | dismiss(self) 60 | } 61 | 62 | private lazy var editMenu: NSMenu = { 63 | let menu = NSMenu() 64 | let saveItem = NSMenuItem() 65 | saveItem.title = "修改" 66 | saveItem.target = self 67 | saveItem.action = #selector(tableViewEditItemClicked) 68 | menu.addItem(saveItem) 69 | let removeItem = NSMenuItem() 70 | removeItem.title = "删除" 71 | removeItem.target = self 72 | removeItem.action = #selector(tableViewDeleteItemClicked) 73 | menu.addItem(removeItem) 74 | return menu 75 | }() 76 | 77 | @objc private func tableViewEditItemClicked(_ sender: AnyObject) { 78 | let row = tableView.clickedRow 79 | guard row >= 0 else { return } 80 | 81 | let result = models 82 | let index = result.index(result.startIndex, offsetBy: row) 83 | let model = result[index] 84 | let vc = APASCKeysEditVC() 85 | vc.titleString = "修改 API 密钥" 86 | vc.spassword = model 87 | vc.updateCompletion = { [weak self] news in 88 | if news.isused { 89 | // 只能有一个是使用的账号,其它为否 90 | self?.models = self?.models.map({ sp in 91 | var spp = sp 92 | return spp.model(sp, false) 93 | }) ?? [] 94 | } 95 | self?.models[index] = news 96 | self?.tableView.reloadData() 97 | } 98 | presentAsSheet(vc) 99 | } 100 | 101 | @objc private func tableViewDeleteItemClicked(_ sender: AnyObject) { 102 | let row = tableView.clickedRow 103 | guard row >= 0 else { return } 104 | 105 | let result = models 106 | let index = result.index(result.startIndex, offsetBy: row) 107 | models.remove(at: index) 108 | tableView.reloadData() 109 | } 110 | 111 | override func viewDidLoad() { 112 | super.viewDidLoad() 113 | setupUI() 114 | } 115 | 116 | func setupUI() { 117 | 118 | tableView.menu = editMenu 119 | tableView.delegate = self 120 | tableView.dataSource = self 121 | 122 | models = InfoCenter.shared.ascKeys 123 | tableView.reloadData() 124 | } 125 | } 126 | 127 | 128 | // MARK: NSTableViewDataSource && NSTableViewDelegate 129 | extension APASCKeysSettingVC: NSTableViewDataSource, NSTableViewDelegate { 130 | func numberOfRows(in tableView: NSTableView) -> Int { 131 | return models.count 132 | } 133 | 134 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 135 | return 30.0 136 | } 137 | 138 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 139 | let result = models 140 | let index = result.index(result.startIndex, offsetBy: row) 141 | let model = result[index] 142 | let identifier = tableColumn!.identifier 143 | let identifierString = identifier.rawValue 144 | 145 | if identifierString == "AccountCell" { 146 | let cellView = tableView.makeView(withIdentifier: identifier, owner: self) as! NSTableCellView 147 | cellView.textField!.stringValue = model.aliasName 148 | return cellView 149 | } 150 | else if identifierString == "IssuerIDCell" { 151 | let cellView = tableView.makeView(withIdentifier: identifier, owner: self) as! NSTableCellView 152 | cellView.textField!.stringValue = model.issuerID 153 | return cellView 154 | } 155 | else if identifierString == "currentUseCell" { 156 | let cellView = tableView.makeView(withIdentifier: identifier, owner: self) as! NSTableCellView 157 | cellView.textField!.stringValue = model.isused ? "✓" : "-" 158 | return cellView 159 | } 160 | else if identifierString == "PrivateKeyID" { 161 | let cellView = tableView.makeView(withIdentifier: identifier, owner: self) as! NSTableCellView 162 | cellView.textField!.stringValue = model.privateKeyID 163 | return cellView 164 | } 165 | else if identifierString == "PrivateKey" { 166 | let cellView = tableView.makeView(withIdentifier: identifier, owner: self) as! NSTableCellView 167 | cellView.textField!.stringValue = model.privateKey 168 | return cellView 169 | } 170 | else { 171 | print("unhandled colum id: \(identifierString)") 172 | } 173 | return nil 174 | } 175 | 176 | 177 | // MARK: 是否可以选中单元格 178 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { 179 | 180 | return true 181 | } 182 | 183 | func tableViewSelectionDidChange(_ notification: Notification) { 184 | let table = notification.object as! NSTableView 185 | table.deselectRow(table.selectedRow) 186 | } 187 | 188 | } 189 | 190 | -------------------------------------------------------------------------------- /AppleParty/Resources/Transporter/iap_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {team_id} 4 | 5 | {SKU} 6 | 7 | 8 | 9 | 10 | com.app.1usd 11 | 1ud(2~64 个字符) 12 | 13 | consumable 14 | 15 | 16 | true 17 | 3 18 | 19 | 20 | 21 | 22 | 2~30个字符 23 | 至少10~45个字符 24 | 25 | 26 | 中文2~30个字符 27 | 至少10~45个字符 28 | 29 | 30 | 31 | 32 | 636132 33 | IMG_5180.PNG 34 | xxxxx 35 | 36 | Some notes for the reviewer.(2~4000字符) 37 | 38 | 39 | 40 | com.app.3usd 41 | 3ud(2~64 个字符) 42 | non-consumable 43 | 44 | 45 | true 46 | 3 47 | 48 | 49 | 50 | 51 | 2~30个字符 52 | 至少10~45个字符 53 | 54 | 55 | 56 | 57 | 636132 58 | IMG_5180.PNG 59 | xxxxx 60 | 61 | Some notes for the reviewer.(2~4000字符) 62 | 63 | 64 | 65 | 66 | 订阅群组显示名称(2-75) 67 | App 名称显示选项:使用自定义名称(2-30) 68 | 69 | 70 | 71 | every_movie_in_the_world_plus_1month 72 | 参考名称 73 | auto-renewable 74 | 1 Month 75 | 7 Days 76 | true 77 | 78 | 79 | 订阅显示名称 80 | 订阅描述Every movie ever plus. 81 | 82 | 83 | 84 | 636132 85 | IMG_5180.PNG 86 | xxxxx 87 | 88 | Some notes for the reviewer.(2~4000字符) 89 | 90 | 91 | 92 | 93 | com.app.6usd 94 | 6ud(2~64 个字符) 95 | subscription 96 | 97 | 98 | true 99 | 49 100 | 101 | 102 | 103 | 104 | 2~30个字符 105 | 至少10~45个字符 106 | 107 | 108 | 109 | 110 | 636132 111 | IMG_5180.PNG 112 | xxxxx 113 | 114 | Some notes for the reviewer.(2~4000字符) 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /AppleParty/SparkleUpdate/AppleParty-release.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | AppleParty 更新说明 8 | 9 | 17 | 18 |
19 |

v3.8.02025-09-29

20 |
    21 |
  • 修复苹果登录内购价格调整失败的问题
  • 22 |
23 |
24 |
25 |

v3.7.02024-10-29

26 |
    27 |
  • 修复苹果登录接口升级为 Secure Remote Password (SRP),导致登录失败问题
  • 28 |
  • 增加上传批量内购时,是否显示 ASC API 请求速率的阈值的开关
  • 29 |
  • 修复切换账号接口导致财务报表下载失败问题
  • 30 |
31 |
32 |
33 |

v3.3.02024-01-26

34 |
    35 |
  • 修复 App Store Connect API 3.2 字段弃用导致无法上传批量内购问题
  • 36 |
37 |
38 |
39 |

v3.2.02023-12-29

40 |
    41 |
  • 新增内购上传时,显示接口速率限制和剩余的次数
  • 42 |
  • 修复切换账号接口变更问题
  • 43 |
44 |
45 |
46 |

v3.1.02023-04-28

47 |
    48 |
  • 新增内购凭证验证工具,用于校验 IAP 凭证
  • 49 |
  • 批量内购创建:审核备注原来有值,而表格未填写值时,则使用 ASC 后台的原值
  • 50 |
51 |
52 |
53 |

v3.0.02023-04-23

54 |
    55 |
  • 新增支持苹果新价格机制,基准国家和定价价格等配置
  • 56 |
  • 内购支持销售范围配置,包括国家或地区,将来新国家/地区自动提供销售等
  • 57 |
  • 注意:需要使用新的内购价格表格!
  • 58 |
  • 打开 App 默认居中显示
  • 59 |
60 |
61 |
62 |

v2.1.12023-02-01

63 |
    64 |
  • 修复 MBProgressHUD-OSX 仓库删除导致无法构建问题
  • 65 |
66 |
67 |
68 |

v2.1.02022-12-25

69 |
    70 |
  • 通过 AppStoreConnect API 批量创建内购商品功能
  • 71 |
  • 增加上传 IPA 文件功能
  • 72 |
73 |
74 |
75 |

v2.0.52022-07-13

76 |
    77 |
  • 修复苹果 App 列表接口变更导致 App 功能无法使用的问题
  • 78 |
79 |
80 |
81 |

v2.0.42022-04-10

82 |
    83 |
  • 修复勾选信任设备后不生效的问题
  • 84 |
85 |
86 |
87 |

v2.0.32022-04-09

88 |
    89 |
  • 增加是否记住密码、信任设备的勾选项
  • 90 |
  • 切换账号从双点确认改为单击确认切换
  • 91 |
  • 修复和完善一些Bug和体验的问题,欢迎大家反馈~
  • 92 |
93 |
94 |
95 |

v2.0.22022-04-07

96 |
    97 |
  • 修复账号切换登陆后没有记住登陆状态的问题
  • 98 |
  • 优化账号登陆选择弹窗,从双击选择改为单击选择
  • 99 |
  • 优化完善一些体验细节,欢迎大家反馈~
  • 100 |
101 |
102 |
103 |

v2.0.12022-04-06

104 |
    105 |
  • 修复导出内购项目表csv文件中文乱码和分隔可能错误的问题
  • 106 |
  • 修复更新弹窗内容显示为文本的问题
  • 107 |
108 |
109 |
110 |

What's AppleParty

111 |
112 | AppleParty 是三七互娱旗下37手游iOS团队研发,实现快速操作 App Store Connect 后台的自动化 macOS 工具。 113 |
    114 |
  • 内购买项目管理;
  • 115 |
  • 批量内购买项目创建和更新;
  • 116 |
  • 批量商店图和预览视频上传和更新;
  • 117 |
  • 邮件发送工具;
  • 118 |
  • 二维码扫描和生成工具;
  • 119 |
  • 数据报表下载和同步(未来开源);
  • 120 |
  • 更多功能,敬请期待~
  • 121 |
122 |
123 |
124 | 125 | 277 | 278 | 279 | -------------------------------------------------------------------------------- /AppleParty/QRcodeView/APQRcodeVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APQRcodeVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/24. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APQRcodeVC: NSViewController { 12 | 13 | @IBOutlet weak var inputTextField: NSTextField! 14 | @IBOutlet weak var qrcodeSizeBtn: NSPopUpButton! 15 | @IBOutlet weak var createQrcodeBtn: NSButton! 16 | @IBOutlet weak var qrcodeImageView: NSImageView! 17 | @IBOutlet weak var copyQrcodeBtn: NSButton! 18 | @IBOutlet weak var saveQrcodeBtn: NSButton! 19 | @IBOutlet weak var shareQrcodeBtn: NSButton! 20 | @IBOutlet weak var shareQrcodeByAirDropBtn: NSButton! 21 | @IBOutlet weak var scanQrcodeBtn: NSButton! 22 | @IBOutlet weak var messageLbl: NSTextField! 23 | @IBOutlet weak var textScrollView: NSScrollView! 24 | @IBOutlet weak var textView: NSTextView! 25 | 26 | @IBAction func createQrcode(_ sender: Any) { 27 | let str = inputTextField!.stringValue 28 | if str.isEmpty { 29 | enableQrcode(false) 30 | statusMessage("") 31 | NSAlert.show("请输出需要生成二维码的文本!") 32 | return 33 | } 34 | 35 | let img = createQRImage(str, NSMakeSize(360, 360)) 36 | qrcodeImageView.image = img 37 | enableQrcode(true) 38 | statusMessage("二维码生成成功") 39 | } 40 | 41 | @IBAction func copyQrcode(_ sender: Any) { 42 | let str = inputTextField!.stringValue 43 | if !str.isEmpty { 44 | let img = createQRImage(str, getImageSize()) 45 | let pb = NSPasteboard.general 46 | pb.clearContents() 47 | if pb.writeObjects([img as NSPasteboardWriting]) { 48 | statusMessage("Copy QRCode to clipboard") 49 | } else { 50 | statusMessage("Failed to copy QRCode to clipboard") 51 | } 52 | } 53 | } 54 | 55 | 56 | @IBAction func saveQrcode(_ sender: Any) { 57 | let str = inputTextField!.stringValue 58 | if str.isEmpty { 59 | return statusMessage("请填写有效的文本内容!") 60 | } 61 | 62 | let savePanel = NSSavePanel() 63 | savePanel.title = "Save QRCode As File" 64 | savePanel.canCreateDirectories = true 65 | savePanel.allowedFileTypes = ["png"] 66 | savePanel.isExtensionHidden = false 67 | savePanel.nameFieldStringValue = getImgaeName() + ".png" 68 | savePanel.becomeKey() 69 | let result = savePanel.runModal() 70 | if (result == .OK && (savePanel.url) != nil) { 71 | let img = createQRImage(str, getImageSize()) 72 | let imgRep = NSBitmapImageRep(data: img.tiffRepresentation!) 73 | let data = imgRep?.representation(using: NSBitmapImageRep.FileType.png, properties: [:]) 74 | try! data?.write(to: savePanel.url!) 75 | statusMessage("Save QRCode to \(savePanel.url!.absoluteString)") 76 | } 77 | } 78 | 79 | @IBAction func shareQrcode(_ sender: Any) { 80 | let str = inputTextField!.stringValue 81 | if str.isEmpty { 82 | return statusMessage("请填写有效的文本内容!") 83 | } 84 | 85 | let img = createQRImage(str, getImageSize()) 86 | let picker = NSSharingServicePicker(items: [img]) 87 | picker.delegate = self 88 | picker.show(relativeTo: .zero, of: sender as! NSView, preferredEdge: .maxX) 89 | } 90 | 91 | @IBAction func shareQrcodeByAirDrop(_ sender: Any) { 92 | let str = inputTextField!.stringValue 93 | if str.isEmpty { 94 | return statusMessage("请填写有效的文本内容!") 95 | } 96 | 97 | let img = createQRImage(str, getImageSize()) 98 | let service = NSSharingService(named: .sendViaAirDrop)! 99 | let items: [NSImage] = [img] 100 | if service.canPerform(withItems: items) { 101 | service.delegate = self 102 | service.perform(withItems: items) 103 | } else { 104 | statusMessage("Cannot perform AirDrop!") 105 | } 106 | } 107 | 108 | @IBAction func scanQrcode(_ sender: Any) { 109 | enableQrcode(false) 110 | qrcodeImageView.isHidden = true 111 | textView.string = "" 112 | textScrollView.isHidden = false 113 | // scan QRCode 114 | let dict = scanQRCodeOnScreen() as! [String:Any] 115 | let data = dict["qrcode"] as! Array 116 | if data.isEmpty { 117 | textView.string = "Not found valid QRCode of screen!" 118 | return 119 | } 120 | // output message 121 | appendToTextView("识别到二维码个数:\(data.count)\n", coreText: "\(data.count)") 122 | for (index, element) in data.enumerated() { 123 | let k = index + 1 124 | appendToTextView("\n第\(k)个二维码内容:\n【\n\(element)\n】\n", coreText: element) 125 | } 126 | } 127 | 128 | override func viewDidAppear() { 129 | super.viewDidAppear() 130 | inputTextField.becomeFirstResponder() 131 | } 132 | 133 | override func viewDidLoad() { 134 | super.viewDidLoad() 135 | setupUI() 136 | } 137 | 138 | func setupUI() { 139 | inputTextField.delegate = self 140 | enableQrcode(false) 141 | qrcodeImageView.image = NSImage(named: "QRcode") 142 | } 143 | 144 | func enableQrcode(_ enable: Bool) { 145 | copyQrcodeBtn.isEnabled = enable 146 | saveQrcodeBtn.isEnabled = enable 147 | shareQrcodeBtn.isEnabled = enable 148 | shareQrcodeByAirDropBtn.isEnabled = enable 149 | textScrollView.isHidden = true 150 | qrcodeImageView.isHidden = false 151 | if !enable { 152 | statusMessage("") 153 | qrcodeImageView.image = NSImage(named: "QRcode") 154 | } 155 | } 156 | 157 | func statusMessage(_ msg: String) { 158 | messageLbl.stringValue = msg.count == 0 ? "" : "提示:\(msg)" 159 | } 160 | 161 | func getImageSize() -> NSSize { 162 | let wh = CGFloat(qrcodeSizeBtn?.selectedTag() ?? 500) 163 | let size = NSMakeSize(wh, wh) 164 | return size 165 | } 166 | 167 | func getImgaeName() -> String { 168 | let dateFormatter : DateFormatter = DateFormatter() 169 | dateFormatter.dateFormat = "HH-mm-ss" 170 | let date = Date() 171 | let dateString = dateFormatter.string(from: date) 172 | return "AppleParty_qrcode-" + dateString 173 | } 174 | 175 | func appendToTextView(_ text: String, coreText: String) { 176 | let attributes = [NSAttributedString.Key.foregroundColor: NSColor.labelColor] 177 | let secondAttributes = [NSAttributedString.Key.foregroundColor: #colorLiteral(red: 0.3211918473, green: 0.7199308276, blue: 1, alpha: 1)] 178 | let attr = NSMutableAttributedString.init(string: text, attributes: attributes) 179 | attr.addAttributes(secondAttributes, range: (text as NSString).range(of: coreText)) 180 | textView.textStorage?.append(attr) 181 | textView.scrollRangeToVisible(NSMakeRange(textView.string.count, 0)) 182 | } 183 | } 184 | 185 | 186 | extension APQRcodeVC: NSTextFieldDelegate { 187 | func controlTextDidChange(_ obj: Notification) { 188 | let textField = obj.object as! NSTextField 189 | if textField.stringValue.isEmpty { 190 | enableQrcode(false) 191 | } 192 | } 193 | } 194 | 195 | 196 | extension APQRcodeVC: NSSharingServicePickerDelegate, NSSharingServiceDelegate { 197 | 198 | 199 | } 200 | -------------------------------------------------------------------------------- /AppleParty/LoginView/APLogin2FAVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APLogin2FAVC.swift 3 | // AppleParty 4 | // 5 | // Created by HTC on 2022/3/17. 6 | // Copyright © 2022 37 Mobile Games. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class APLogin2FAVC: NSViewController { 12 | 13 | public var cancelHandle: (() -> Void)? 14 | public var successHandle: (() -> Void)? 15 | 16 | @IBOutlet weak var phoneListBtn: NSPopUpButton! 17 | @IBOutlet weak var sendCodeBtn: NSButton! 18 | @IBOutlet weak var voiceCodeBtn: NSButton! 19 | @IBOutlet weak var phoneCodeView: NSTextField! 20 | @IBOutlet weak var tipsWarningView: NSTextField! 21 | @IBOutlet weak var trusDeviceBtn: NSButton! 22 | @IBOutlet weak var indicatorView: NSProgressIndicator! 23 | @IBOutlet weak var verifyBtn: NSButton! 24 | 25 | private var numbers: [PNumber] = [] //验证手机号码列表 26 | private var isPhoneSecurity = false //是否通过手机验证码来验证 27 | // 验证码倒计时 28 | private var verifyCodeTimer: Timer? 29 | private var lastTime: Int = 30 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | phoneCodeView.delegate = self 34 | fetchPhoneList() 35 | trusDeviceBtn.state = InfoCenter.shared.trusDevice ? .on : .off 36 | } 37 | 38 | @IBAction func clickedCancelBtn(_ sender: NSButton) { 39 | closeView() 40 | cancelHandle?() 41 | } 42 | 43 | @IBAction func clickedSendCodeBtn(_ sender: NSButton) { 44 | submitSecurityCode() 45 | } 46 | 47 | 48 | @IBAction func changeVoiceCodeBtn(_ sender: NSButton) { 49 | sendCodeBtn.title = sender.state == .on ? "拨打语音验证码" : "发送短信验证码" 50 | } 51 | 52 | 53 | @IBAction func clickedVerifyBtn(_ sender: NSButton) { 54 | verifySecurityCode() 55 | } 56 | 57 | @IBAction func clickedTrusDeviceBtn(_ sender: NSButton) { 58 | InfoCenter.shared.trusDevice = sender.state == .on ? true : false 59 | } 60 | } 61 | 62 | 63 | // MARK: - 网络请求 64 | extension APLogin2FAVC { 65 | 66 | func fetchPhoneList() { 67 | APClient.verifySecurityPhone(mode: "sms", phoneid: 0).request(showLoading: true) { [weak self] result, response, error in 68 | if let err = error, let type = APClientErrorCode(rawValue: err.code) { 69 | switch type { 70 | case .privacyAcknowledgementRequired: 71 | // 传了无效phoneid,进入选择手机号的流程 72 | self?.phoneListBtn.removeAllItems() 73 | let model = PhoneNumbers(body: result) 74 | self?.numbers = model.numbers 75 | for number in model.numbers { 76 | self?.phoneListBtn.addItem(withTitle: number.num) 77 | } 78 | self?.phoneListBtn.selectItem(at: 0) 79 | self?.showTips("一条包含验证码的信息已发送至您的设备。可输入设备验证码后点击验证以继续。\n或者点击“发送短信验证码”获取短信验证码。") 80 | default: 81 | APHUD.hide(message: err.localizedDescription) 82 | } 83 | } 84 | } 85 | } 86 | 87 | func submitSecurityCode() { 88 | isPhoneSecurity = true 89 | let mode = voiceCodeBtn.state == .on ? "voice" : "sms" 90 | let phoneId = numbers[phoneListBtn.indexOfSelectedItem].id 91 | APClient.verifySecurityPhone(mode: mode, phoneid: phoneId).request(showLoading: true) { [weak self] result, response, error in 92 | let code = response?.statusCode 93 | if [200, 423].contains(code) { 94 | let msg = self?.voiceCodeBtn.state == .on ? "请求拨打语音电话,请收听~" : "验证码已发送,请查收~" 95 | self?.showTips(msg) 96 | self?.verifyCodeCountdown() 97 | } else { 98 | self?.showTips("\(code ?? 0),\(error.debugDescription)") 99 | } 100 | } 101 | } 102 | 103 | func verifySecurityCode() { 104 | let code = phoneCodeView.stringValue 105 | let phoneId = numbers[phoneListBtn.indexOfSelectedItem].id 106 | let mode = voiceCodeBtn.state == .on ? "voice" : "sms" 107 | let type = isPhoneSecurity ? APClient.SecurityCode.sms(code: code, phoneNumberId: phoneId, mode: mode) : APClient.SecurityCode.device(code: code) 108 | 109 | viewEnabled(false) 110 | APClient.submitSecurityCode(code: type).request { [weak self] result, response, error in 111 | self?.viewEnabled(true) 112 | let code = response?.statusCode 113 | switch code { 114 | case 200, 201, 202, 203, 204: 115 | self?.validateSession() 116 | case 400: 117 | let errors = dictionaryArray(result["service_errors"]) 118 | let msg = string(from: errors.first?["message"]) 119 | self?.showTips(msg) 120 | default: 121 | self?.showTips("\(code ?? 0),\(error.debugDescription)") 122 | } 123 | } 124 | } 125 | 126 | func validateSession() { 127 | viewEnabled(false) 128 | APClient.signInSession.request { [weak self] result, response, error in 129 | self?.viewEnabled(true) 130 | let code = response?.statusCode 131 | switch code { 132 | case 200, 201: 133 | UserCenter.shared.isAuthorized = true 134 | self?.successHandle?() 135 | self?.closeView() 136 | default: 137 | let errors = dictionaryArray(result["serviceErrors"]) 138 | let msg = string(from: errors.first?["message"]) 139 | self?.showTips(msg.isEmpty ? error.debugDescription : msg) 140 | } 141 | } 142 | } 143 | } 144 | 145 | // MARK: - 内部方法 146 | extension APLogin2FAVC { 147 | 148 | func closeView() { 149 | guard let window = view.window, let parent = window.sheetParent 150 | else { return } 151 | parent.endSheet(window) 152 | } 153 | 154 | func showTips(_ text: String) { 155 | if text.isEmpty { 156 | tipsWarningView.isHidden = true 157 | tipsWarningView.stringValue = "" 158 | } else { 159 | tipsWarningView.stringValue = text 160 | tipsWarningView.isHidden = false 161 | } 162 | } 163 | 164 | func viewEnabled(_ isEnabled: Bool) { 165 | showTips("") 166 | verifyBtn.isEnabled = isEnabled 167 | isEnabled ? indicatorView.stopAnimation(nil) : indicatorView.startAnimation(nil) 168 | } 169 | 170 | func verifyCodeCountdown() { 171 | self.lastTime = 30 172 | self.sendCodeBtn.title = "\(self.lastTime)s 后重试" 173 | self.sendCodeBtn.isEnabled = false 174 | self.verifyCodeTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.verifyCodeTime), userInfo: nil, repeats: true) 175 | } 176 | 177 | // 验证码倒计时 178 | @objc func verifyCodeTime() { 179 | lastTime -= 1 180 | sendCodeBtn.title = "\(self.lastTime)s 后重试" 181 | if lastTime <= 0 { 182 | sendCodeBtn.title = "重新发送验证码" 183 | sendCodeBtn.isEnabled = true 184 | verifyCodeTimer?.invalidate() 185 | } 186 | } 187 | } 188 | 189 | // MARK: - NSTextFieldDelegate 190 | extension APLogin2FAVC: NSTextFieldDelegate { 191 | 192 | func controlTextDidChange(_ obj: Notification) { 193 | if phoneCodeView.stringValue.count == 6 { 194 | verifyBtn.isEnabled = true 195 | } else { 196 | verifyBtn.isEnabled = false 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /AppleParty/Vendors/ITMS/GDataXMLNode.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2008 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | // These node, element, and document classes implement a subset of the methods 17 | // provided by NSXML. While NSXML behavior is mimicked as much as possible, 18 | // there are important differences. 19 | // 20 | // The biggest difference is that, since this is based on libxml2, there 21 | // is no retain model for the underlying node data. Rather than copy every 22 | // node obtained from a parse tree (which would have a substantial memory 23 | // impact), we rely on weak references, and it is up to the code that 24 | // created a document to retain it for as long as any 25 | // references rely on nodes inside that document tree. 26 | 27 | 28 | #import 29 | 30 | // libxml includes require that the target Header Search Paths contain 31 | // 32 | // /usr/include/libxml2 33 | // 34 | // and Other Linker Flags contain 35 | // 36 | // -lxml2 37 | 38 | #ifndef LIBXML_VERSION 39 | // Forward declaration of types when not included. 40 | 41 | struct _xmlNode; 42 | typedef struct _xmlNode xmlNode; 43 | typedef xmlNode* xmlNodePtr; 44 | struct _xmlDoc; 45 | typedef struct _xmlDoc xmlDoc; 46 | typedef xmlDoc *xmlDocPtr; 47 | #endif 48 | 49 | #ifdef GDATA_TARGET_NAMESPACE 50 | // we're using target namespace macros 51 | #import "GDataDefines.h" 52 | #endif 53 | 54 | #undef _EXTERN 55 | #undef _INITIALIZE_AS 56 | #ifdef GDATAXMLNODE_DEFINE_GLOBALS 57 | #define _EXTERN 58 | #define _INITIALIZE_AS(x) =x 59 | #else 60 | #if defined(__cplusplus) 61 | #define _EXTERN extern "C" 62 | #else 63 | #define _EXTERN extern 64 | #endif 65 | #define _INITIALIZE_AS(x) 66 | #endif 67 | 68 | // when no namespace dictionary is supplied for XPath, the default namespace 69 | // for the evaluated tree is registered with the prefix _def_ns 70 | _EXTERN const char* kGDataXMLXPathDefaultNamespacePrefix _INITIALIZE_AS("_def_ns"); 71 | 72 | // Nomenclature for method names: 73 | // 74 | // Node = GData node 75 | // XMLNode = xmlNodePtr 76 | // 77 | // So, for example: 78 | // + (id)nodeConsumingXMLNode:(xmlNodePtr)theXMLNode; 79 | 80 | @class NSArray, NSDictionary, NSError, NSString, NSURL; 81 | @class GDataXMLElement, GDataXMLDocument; 82 | 83 | enum { 84 | GDataXMLInvalidKind = 0, 85 | GDataXMLDocumentKind, 86 | GDataXMLElementKind, 87 | GDataXMLAttributeKind, 88 | GDataXMLNamespaceKind, 89 | GDataXMLProcessingInstructionKind, 90 | GDataXMLCommentKind, 91 | GDataXMLTextKind, 92 | GDataXMLDTDKind, 93 | GDataXMLEntityDeclarationKind, 94 | GDataXMLAttributeDeclarationKind, 95 | GDataXMLElementDeclarationKind, 96 | GDataXMLNotationDeclarationKind 97 | }; 98 | 99 | typedef NSUInteger GDataXMLNodeKind; 100 | 101 | @interface GDataXMLNode : NSObject { 102 | @protected 103 | // NSXMLNodes can have a namespace URI or prefix even if not part 104 | // of a tree; xmlNodes cannot. When we create nodes apart from 105 | // a tree, we'll store the dangling prefix or URI in the xmlNode's name, 106 | // like 107 | // "prefix:name" 108 | // or 109 | // "{http://uri}:name" 110 | // 111 | // We will fix up the node's namespace and name (and those of any children) 112 | // later when adding the node to a tree with addChild: or addAttribute:. 113 | // See fixUpNamespacesForNode:. 114 | 115 | xmlNodePtr xmlNode_; // may also be an xmlAttrPtr or xmlNsPtr 116 | BOOL shouldFreeXMLNode_; // if yes, xmlNode_ will be free'd in dealloc 117 | 118 | // cached values 119 | NSString *cachedName_; 120 | NSArray *cachedChildren_; 121 | NSArray *cachedAttributes_; 122 | } 123 | 124 | + (GDataXMLElement *)elementWithName:(NSString *)name; 125 | + (GDataXMLElement *)elementWithName:(NSString *)name stringValue:(NSString *)value; 126 | + (GDataXMLElement *)elementWithName:(NSString *)name URI:(NSString *)value; 127 | 128 | + (id)attributeWithName:(NSString *)name stringValue:(NSString *)value; 129 | + (id)attributeWithName:(NSString *)name URI:(NSString *)attributeURI stringValue:(NSString *)value; 130 | 131 | + (id)namespaceWithName:(NSString *)name stringValue:(NSString *)value; 132 | 133 | + (id)textWithStringValue:(NSString *)value; 134 | 135 | - (NSString *)stringValue; 136 | - (void)setStringValue:(NSString *)str; 137 | 138 | - (NSUInteger)childCount; 139 | - (NSArray *)children; 140 | - (GDataXMLNode *)childAtIndex:(unsigned)index; 141 | 142 | - (NSString *)localName; 143 | - (NSString *)name; 144 | - (NSString *)prefix; 145 | - (NSString *)URI; 146 | 147 | - (GDataXMLNodeKind)kind; 148 | 149 | - (NSString *)XMLString; 150 | 151 | + (NSString *)localNameForName:(NSString *)name; 152 | + (NSString *)prefixForName:(NSString *)name; 153 | 154 | // This is the preferred entry point for nodesForXPath. This takes an explicit 155 | // namespace dictionary (keys are prefixes, values are URIs). 156 | - (NSArray *)nodesForXPath:(NSString *)xpath namespaces:(NSDictionary *)namespaces error:(NSError **)error; 157 | 158 | // This implementation of nodesForXPath registers namespaces only from the 159 | // document's root node. _def_ns may be used as a prefix for the default 160 | // namespace, though there's no guarantee that the default namespace will 161 | // be consistenly the same namespace in server responses. 162 | - (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error; 163 | 164 | // access to the underlying libxml node; be sure to release the cached values 165 | // if you change the underlying tree at all 166 | - (xmlNodePtr)XMLNode; 167 | - (void)releaseCachedValues; 168 | 169 | @end 170 | 171 | 172 | @interface GDataXMLElement : GDataXMLNode 173 | 174 | - (id)initWithXMLString:(NSString *)str error:(NSError **)error; 175 | 176 | - (NSArray *)namespaces; 177 | - (void)setNamespaces:(NSArray *)namespaces; 178 | - (void)addNamespace:(GDataXMLNode *)aNamespace; 179 | 180 | // addChild adds a copy of the child node to the element 181 | - (void)addChild:(GDataXMLNode *)child; 182 | - (void)removeChild:(GDataXMLNode *)child; 183 | 184 | - (NSArray *)elementsForName:(NSString *)name; 185 | - (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI; 186 | 187 | - (NSArray *)attributes; 188 | - (GDataXMLNode *)attributeForName:(NSString *)name; 189 | - (GDataXMLNode *)attributeForLocalName:(NSString *)name URI:(NSString *)attributeURI; 190 | - (void)addAttribute:(GDataXMLNode *)attribute; 191 | 192 | - (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI; 193 | 194 | @end 195 | 196 | @interface GDataXMLDocument : NSObject { 197 | @protected 198 | xmlDoc* xmlDoc_; // strong; always free'd in dealloc 199 | } 200 | 201 | - (id)initWithXMLString:(NSString *)str options:(unsigned int)mask error:(NSError **)error; 202 | - (id)initWithData:(NSData *)data options:(unsigned int)mask error:(NSError **)error; 203 | 204 | // initWithRootElement uses a copy of the argument as the new document's root 205 | - (id)initWithRootElement:(GDataXMLElement *)element; 206 | 207 | - (GDataXMLElement *)rootElement; 208 | 209 | - (NSData *)XMLData; 210 | 211 | - (void)setVersion:(NSString *)version; 212 | - (void)setCharacterEncoding:(NSString *)encoding; 213 | 214 | // This is the preferred entry point for nodesForXPath. This takes an explicit 215 | // namespace dictionary (keys are prefixes, values are URIs). 216 | - (NSArray *)nodesForXPath:(NSString *)xpath namespaces:(NSDictionary *)namespaces error:(NSError **)error; 217 | 218 | // This implementation of nodesForXPath registers namespaces only from the 219 | // document's root node. _def_ns may be used as a prefix for the default 220 | // namespace, though there's no guarantee that the default namespace will 221 | // be consistenly the same namespace in server responses. 222 | - (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error; 223 | 224 | - (NSString *)description; 225 | @end 226 | --------------------------------------------------------------------------------