├── .gitignore ├── 12306ForMac.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ ├── dream.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── lindahai.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── dream.xcuserdatad │ └── xcschemes │ │ ├── 12306ForMac.xcscheme │ │ └── xcschememanagement.plist │ └── lindahai.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── 12306ForMac.xcscheme │ └── xcschememanagement.plist ├── 12306ForMac ├── 12306ForMac-Bridging-Header.h ├── 12306ForMac.entitlements ├── AppDelegate.swift ├── BaseWindowController.swift ├── Images.xcassets │ ├── Add.imageset │ │ ├── Add.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ ├── Contents.json │ ├── DefineFilter.imageset │ │ ├── Contents.json │ │ └── DefineFilter.png │ ├── Direction.imageset │ │ ├── Contents.json │ │ └── Direction.png │ ├── Filter.imageset │ │ ├── Contents.json │ │ └── Filter.png │ ├── Reminder.imageset │ │ ├── Contents.json │ │ └── Reminder.png │ ├── Reverse.imageset │ │ ├── Contents.json │ │ └── Reverse.png │ └── User.imageset │ │ ├── Contents.json │ │ └── User.png ├── Info.plist ├── MainWindowController.swift ├── MainWindowController.xib ├── Model │ ├── AutoSubmitParams.swift │ ├── CaptchaImageParam.swift │ ├── ConfirmSingleForQueueAsysParam.swift │ ├── ConfirmSingleForQueueParam.swift │ ├── GetQueueCountAsysParam.swift │ ├── GetQueueCountParam.swift │ ├── LeftTicketParam.swift │ ├── MainModel.swift │ ├── OrderDTO.swift │ ├── PassengerDTO.swift │ ├── PaycheckNewParam.swift │ ├── QueryByTrainCodeParam.swift │ ├── QueryLeftNewDTO.swift │ ├── QueryOrderParam.swift │ ├── QueryOrderWaitTimeResult.swift │ ├── QueryTrainPriceParam.swift │ ├── SubmitOrderParams.swift │ ├── TicketConstants.swift │ ├── TicketQueueCountResult.swift │ └── TrainCodeDetail.swift ├── Notifications.swift ├── OrderViewControllers │ ├── OrderViewController.swift │ ├── OrderViewController.xib │ ├── PayWindowController.swift │ └── PayWindowController.xib ├── Preferences │ ├── AdvancedPreferenceManager.swift │ ├── AdvancedPreferenceViewController.swift │ ├── AdvancedPreferenceViewController.xib │ ├── FilterPreferenceViewController.swift │ ├── FilterPreferenceViewController.xib │ ├── GeneralPreferenceManager.swift │ ├── GeneralPreferenceViewController.swift │ ├── GeneralPreferenceViewController.xib │ ├── ReminderPreferenceViewController.swift │ └── ReminderPreferenceViewController.xib ├── RealmModel │ ├── DataManager.swift │ └── User.swift ├── Resources │ └── Credits.rtf ├── Service │ ├── Dama.swift │ ├── DamaError.swift │ ├── Service+Login.swift │ ├── Service+Order.swift │ ├── Service+QueryOrder.swift │ ├── Service+QueryTicket.swift │ ├── Service+Utilities.swift │ ├── Service.swift │ └── ServiceError.swift ├── Sheets │ ├── LoginWindowController.swift │ ├── LoginWindowController.xib │ ├── SubmitWindowController.swift │ └── SubmitWindowController.xib ├── StationData.swift ├── TicketViewControllers │ ├── PassengerSelectViewController.swift │ ├── PassengerSelectViewController.xib │ ├── PassengerViewController.swift │ ├── PassengerViewController.xib │ ├── TicketQueryViewController.swift │ ├── TicketQueryViewController.xib │ ├── TicketTaskManagerWindowController.swift │ ├── TicketTaskManagerWindowController.xib │ ├── TrainCodeDetailViewController.swift │ ├── TrainCodeDetailViewController.xib │ ├── TrainFilterWindowController.swift │ └── TrainFilterWindowController.xib ├── UserControls │ ├── AutoCompleteTextField.swift │ ├── ClickableDatePicker.swift │ ├── ContentBackgroundView.swift │ ├── FilterTrainCodeTransformer.swift │ ├── GlassView.swift │ ├── InfoButton.swift │ ├── LoginButton.swift │ ├── LunarCalendar │ │ ├── LunarCalendarView.swift │ │ ├── LunarCalendarView.xib │ │ └── LunarSolarConverter.swift │ ├── NSTableView+ContextMenu.swift │ ├── RandCodeImageView.swift │ ├── RandCodeImageView2.swift │ ├── Theme.swift │ ├── TrainCodeDetailHeaderCell.swift │ ├── TrainInfoTableCellView.swift │ ├── TrainStationTableCellView.swift │ ├── TrainTableRowView.swift │ ├── URLButton.swift │ └── UrlLabel.swift ├── Utilities │ ├── CalendarManager.swift │ ├── FSPreventSystemSleep.h │ ├── FSPreventSystemSleep.m │ ├── Notifications.swift │ ├── NotifySpeaker.swift │ ├── QueryDefaultManager.swift │ ├── ReminderManager.swift │ └── SwiftyRegex.swift ├── station_name.js └── zh-Hans.lproj │ └── MainMenu.xib ├── Cartfile ├── Cartfile.resolved ├── DJProgressHUD ├── DJActivityIndicator.h ├── DJActivityIndicator.m ├── DJLayerView.h ├── DJLayerView.m ├── DJTipHUD.h └── DJTipHUD.m ├── LICENSE.txt ├── README.md └── screenshot ├── 12306ForMac.jpg └── donate.png /.gitignore: -------------------------------------------------------------------------------- 1 | 12306ForMac.xcworkspace/xcuserdata/ 2 | 12306ForMac.xcworkspace/xcuserdata/* 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData/ 7 | Build/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | 21 | ## Other 22 | *.moved-aside 23 | *.xccheckout 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | 65 | Pods/ 66 | Pods/* 67 | -------------------------------------------------------------------------------- /12306ForMac.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /12306ForMac.xcodeproj/project.xcworkspace/xcuserdata/dream.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac.xcodeproj/project.xcworkspace/xcuserdata/dream.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /12306ForMac.xcodeproj/project.xcworkspace/xcuserdata/lindahai.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac.xcodeproj/project.xcworkspace/xcuserdata/lindahai.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /12306ForMac.xcodeproj/xcuserdata/dream.xcuserdatad/xcschemes/12306ForMac.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /12306ForMac.xcodeproj/xcuserdata/dream.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | 12306ForMac.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | F11EB4591B6A368E000063E4 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /12306ForMac.xcodeproj/xcuserdata/lindahai.xcuserdatad/xcschemes/12306ForMac.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /12306ForMac.xcodeproj/xcuserdata/lindahai.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | 12306ForMac.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | F11EB4591B6A368E000063E4 16 | 17 | primary 18 | 19 | 20 | F11EB4691B6A368E000063E4 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /12306ForMac/12306ForMac-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Train12306-Swift.h 3 | // 4 | // 5 | // Created by fancymax on 15/7/31. 6 | // 7 | // 8 | 9 | #import "DJTipHUD.h" 10 | #import "DJLayerView.h" 11 | #import 12 | 13 | #import "FSPreventSystemSleep.h" 14 | 15 | -------------------------------------------------------------------------------- /12306ForMac/12306ForMac.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.personal-information.calendars 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /12306ForMac/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Train12306 4 | // 5 | // Created by fancymax on 15/7/30. 6 | // Copyright (c) 2015年 fancy. All rights reserved. 7 | // 8 | import Cocoa 9 | import XCGLogger 10 | 11 | var APP_LOG_PATH = "" 12 | var APP_LOG_DIRECTORY = "" 13 | 14 | let logger: XCGLogger = { 15 | // Setup XCGLogger 16 | let log = XCGLogger(identifier: "advancedLogger", includeDefaultDestinations: false) 17 | 18 | let bundleId = Bundle.main.bundleIdentifier! 19 | let fileName = "\(bundleId).txt" 20 | 21 | APP_LOG_DIRECTORY = "\(NSHomeDirectory())/Library/Logs/\(bundleId)/" 22 | APP_LOG_PATH = "\(APP_LOG_DIRECTORY)/\(fileName)" 23 | 24 | let isExistDirectory:Bool = FileManager.default.fileExists(atPath: APP_LOG_DIRECTORY, isDirectory: nil) 25 | if !isExistDirectory { 26 | do{ 27 | try FileManager.default.createDirectory(atPath: APP_LOG_DIRECTORY, withIntermediateDirectories: true, attributes: nil) 28 | } 29 | catch { 30 | print("createDirectoryAtPath fail,can't log") 31 | } 32 | } 33 | 34 | // Create a destination for the system console log (via NSLog) 35 | let systemDestination = AppleSystemLogDestination(identifier: "advancedLogger.systemDestination") 36 | 37 | // Optionally set some configuration options 38 | systemDestination.outputLevel = .info 39 | systemDestination.showLogIdentifier = false 40 | systemDestination.showFunctionName = false 41 | systemDestination.showLevel = false 42 | systemDestination.showFileName = false 43 | systemDestination.showLineNumber = false 44 | systemDestination.showDate = false 45 | log.add(destination: systemDestination) 46 | 47 | let autoRotatingFileDestination = AutoRotatingFileDestination(writeToFile:APP_LOG_PATH) 48 | autoRotatingFileDestination.targetMaxLogFiles = 10 49 | 50 | autoRotatingFileDestination.outputLevel = .info 51 | autoRotatingFileDestination.showLogIdentifier = false 52 | autoRotatingFileDestination.showFunctionName = false 53 | autoRotatingFileDestination.showLevel = false 54 | autoRotatingFileDestination.showFileName = false 55 | autoRotatingFileDestination.showLineNumber = false 56 | autoRotatingFileDestination.showDate = true 57 | log.add(destination:autoRotatingFileDestination) 58 | 59 | // Add basic app info, version info etc, to the start of the logs 60 | log.logAppDetails() 61 | 62 | 63 | return log 64 | }() 65 | 66 | @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { 67 | 68 | var mainController:MainWindowController? 69 | 70 | func applicationDidFinishLaunching(_ aNotification: Notification) { 71 | 72 | let mainController = MainWindowController(windowNibName: "MainWindowController") 73 | mainController.showWindow(self) 74 | 75 | self.mainController = mainController 76 | logger.info("Application start") 77 | logger.info("dama = \(AdvancedPreferenceManager.sharedInstance.isUseDama)") 78 | } 79 | 80 | @IBAction func openDebugFile(_ sender:AnyObject) { 81 | NSWorkspace.shared().openFile(APP_LOG_PATH) 82 | } 83 | 84 | @IBAction func openDebugDirectory(_ sender:AnyObject) { 85 | NSWorkspace.shared().openFile(APP_LOG_DIRECTORY) 86 | } 87 | 88 | func applicationWillTerminate(_ aNotification: Notification) { 89 | // Insert code here to tear down your application 90 | } 91 | 92 | func applicationShouldTerminateAfterLastWindowClosed(_ sender:NSApplication)->Bool { 93 | return true 94 | } 95 | 96 | // func applicationWillResignActive(_ notification: Notification) { 97 | // print("applicationWillResignActive") 98 | // } 99 | // 100 | // func applicationWillBecomeActive(_ notification: Notification) { 101 | // print("applicationWillBecomeActive") 102 | // } 103 | 104 | } 105 | 106 | -------------------------------------------------------------------------------- /12306ForMac/BaseWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseWindowController.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/12/1. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class BaseWindowController: NSWindowController { 12 | 13 | func showTip(_ tip:String) { 14 | DJTipHUD.showStatus(tip, from: self.window?.contentView) 15 | } 16 | 17 | func startLoadingTip(_ tip:String) 18 | { 19 | DJLayerView.showStatus(tip, from: self.window?.contentView) 20 | } 21 | 22 | func stopLoadingTip(){ 23 | DJLayerView.dismiss() 24 | } 25 | 26 | func dismissWithModalResponse(_ response:NSModalResponse) 27 | { 28 | if window != nil { 29 | if window!.sheetParent != nil { 30 | window!.sheetParent!.endSheet(window!,returnCode: response) 31 | } 32 | } 33 | } 34 | } 35 | 36 | class BaseViewController: NSViewController{ 37 | func showTip(_ tip:String){ 38 | DJTipHUD.showStatus(tip, from: self.view) 39 | } 40 | 41 | func startLoadingTip(_ tip:String) 42 | { 43 | DJLayerView.showStatus(tip, from: self.view) 44 | } 45 | 46 | func stopLoadingTip(){ 47 | DJLayerView.dismiss() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/Add.imageset/Add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/Add.imageset/Add.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/Add.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Add.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/DefineFilter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "DefineFilter.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/DefineFilter.imageset/DefineFilter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/DefineFilter.imageset/DefineFilter.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/Direction.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Direction.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/Direction.imageset/Direction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/Direction.imageset/Direction.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/Filter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Filter.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/Filter.imageset/Filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/Filter.imageset/Filter.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/Reminder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Reminder.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/Reminder.imageset/Reminder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/Reminder.imageset/Reminder.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/Reverse.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Reverse.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/Reverse.imageset/Reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/Reverse.imageset/Reverse.png -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/User.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "User.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /12306ForMac/Images.xcassets/User.imageset/User.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/12306ForMac/Images.xcassets/User.imageset/User.png -------------------------------------------------------------------------------- /12306ForMac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.6.8 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 35 23 | LSApplicationCategoryType 24 | public.app-category.utilities 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | 12306.cn 32 | 33 | NSIncludesSubdomains 34 | 35 | NSThirdPartyExceptionAllowsInsecureHTTPLoads 36 | 37 | NSThirdPartyExceptionRequiresForwardSecrecy 38 | 39 | 40 | api.dama2.com 41 | 42 | NSIncludesSubdomains 43 | 44 | NSThirdPartyExceptionAllowsInsecureHTTPLoads 45 | 46 | NSThirdPartyExceptionRequiresForwardSecrecy 47 | 48 | 49 | kyfw.12306.cn 50 | 51 | NSIncludesSubdomains 52 | 53 | NSThirdPartyExceptionAllowsInsecureHTTPLoads 54 | 55 | NSThirdPartyExceptionRequiresForwardSecrecy 56 | 57 | 58 | 59 | 60 | NSHumanReadableCopyright 61 | Copyright © 2015-2017年 Fancy Apps. All rights reserved. 62 | NSMainNibFile 63 | MainMenu 64 | NSPrincipalClass 65 | NSApplication 66 | 67 | 68 | -------------------------------------------------------------------------------- /12306ForMac/Model/AutoSubmitParams.swift: -------------------------------------------------------------------------------- 1 | // 2 | // autoSubmitParams.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/12/13. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct AutoSubmitParams{ 12 | 13 | init(with ticket:QueryLeftNewDTO, purposeCode:String,passengerTicket:String,oldPassenger:String) { 14 | secretStr = ticket.SecretStr! 15 | train_date = ticket.trainDateStr 16 | purpose_codes = purposeCode 17 | query_from_station_name = ticket.FromStationName! 18 | query_to_station_name = ticket.ToStationName! 19 | 20 | passengerTicketStr = passengerTicket 21 | oldPassengerStr = oldPassenger 22 | } 23 | 24 | var secretStr = "" 25 | 26 | var train_date = "" 27 | 28 | let tour_flag = "dc" 29 | 30 | var purpose_codes = "ADULT" 31 | 32 | var query_from_station_name = "" 33 | 34 | var query_to_station_name = "" 35 | 36 | let cancel_flag = "2" 37 | 38 | let bed_level_order_num = "000000000000000000000000000000" 39 | 40 | var passengerTicketStr = "" 41 | var oldPassengerStr = "" 42 | 43 | func ToPostParams()->[String:String]{ 44 | return [ 45 | "secretStr":secretStr, 46 | "train_date":train_date,//2015-11-17 47 | "tour_flag":tour_flag, 48 | "purpose_codes":purpose_codes, 49 | "query_from_station_name":query_from_station_name, 50 | "query_to_station_name":query_to_station_name, 51 | "cancel_flag":cancel_flag, 52 | "bed_level_order_num":bed_level_order_num, 53 | "passengerTicketStr":passengerTicketStr, 54 | "oldPassengerStr":oldPassengerStr] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /12306ForMac/Model/CaptchaImageParam.swift: -------------------------------------------------------------------------------- 1 | // 2 | // captchaImageParam.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2017/9/28. 6 | // Copyright © 2017年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct CaptchaImageParam { 12 | var login_site:String = "E" 13 | var module:String = "login" 14 | var rand:String = "sjrand" 15 | 16 | func ToGetParams()->String{ 17 | let random = CGFloat(Float(arc4random()) / Float(UINT32_MAX))//0~1 18 | return "login_site=\(login_site)&module=\(module)&rand=\(rand)&" + random.description 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /12306ForMac/Model/ConfirmSingleForQueueAsysParam.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfirmSingleForQueueAsysParam.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/12/14. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ConfirmSingleForQueueParamAsys{ 12 | 13 | init(randCodeStr:String,passengerTicket:String,oldPassenger:String) { 14 | passengerTicketStr = passengerTicket 15 | oldPassengerStr = oldPassenger 16 | randCode = randCodeStr 17 | 18 | key_check_isChange = MainModel.key_check_isChange ?? "" 19 | leftTicketStr = MainModel.selectedTicket?.yp_info ?? "" 20 | train_location = MainModel.train_location ?? "" 21 | } 22 | 23 | var passengerTicketStr = "" 24 | var oldPassengerStr = "" 25 | var randCode = "" 26 | var purpose_codes = "ADULT" 27 | var key_check_isChange = "" 28 | var leftTicketStr = "" 29 | var train_location = "" 30 | 31 | var choose_seats = "" 32 | var seatDetailType = "" 33 | 34 | func ToPostParams()->[String:String]{ 35 | return [ 36 | "passengerTicketStr":passengerTicketStr, 37 | "oldPassengerStr":oldPassengerStr, 38 | "randCode":randCode, 39 | "purpose_codes":purpose_codes, 40 | "key_check_isChange":key_check_isChange, 41 | "leftTicketStr":leftTicketStr, 42 | "train_location":train_location, 43 | "choose_seats":choose_seats, 44 | "seatDetailType":seatDetailType, 45 | "_json_att":""] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /12306ForMac/Model/ConfirmSingleForQueueParam.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfirmSingleForQueueParam.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/12/14. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ConfirmSingleForQueueParam{ 12 | 13 | init(randCodeStr:String,passengerTicket:String,oldPassenger:String) { 14 | passengerTicketStr = passengerTicket 15 | oldPassengerStr = oldPassenger 16 | randCode = randCodeStr 17 | 18 | key_check_isChange = MainModel.key_check_isChange ?? "" 19 | leftTicketStr = MainModel.selectedTicket?.yp_info ?? "" 20 | train_location = MainModel.train_location ?? "" 21 | 22 | REPEAT_SUBMIT_TOKEN = MainModel.globalRepeatSubmitToken 23 | } 24 | 25 | var passengerTicketStr = "" 26 | var oldPassengerStr = "" 27 | var randCode = "" 28 | var purpose_codes = "00" 29 | var key_check_isChange = "" 30 | var leftTicketStr = "" 31 | var train_location = "" 32 | 33 | var choose_seats = "" 34 | var seatDetailType = "000" 35 | var roomType = "00" 36 | var dwAll = "N" 37 | 38 | var REPEAT_SUBMIT_TOKEN = "" 39 | 40 | func ToPostParams()->[String:String]{ 41 | return [ 42 | "passengerTicketStr":passengerTicketStr, 43 | "oldPassengerStr":oldPassengerStr, 44 | "randCode":randCode, 45 | "purpose_codes":purpose_codes, 46 | "key_check_isChange":key_check_isChange, 47 | "leftTicketStr":leftTicketStr, 48 | "train_location":train_location, 49 | "choose_seats":choose_seats, 50 | "seatDetailType":seatDetailType, 51 | "roomType":roomType, 52 | "dwAll":dwAll, 53 | "_json_att":"", 54 | "REPEAT_SUBMIT_TOKEN":REPEAT_SUBMIT_TOKEN] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /12306ForMac/Model/GetQueueCountAsysParam.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetQueueCountAsysParam.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/12/14. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct GetQueueCountParamAsys{ 12 | 13 | init(with selectedTicket:QueryLeftNewDTO, seatCode:String) { 14 | train_date = selectedTicket.jsStartTrainDateStr! 15 | train_no = selectedTicket.train_no! 16 | stationTrainCode = selectedTicket.TrainCode! 17 | seatType = seatCode 18 | fromStationTelecode = selectedTicket.FromStationCode! 19 | toStationTelecode = selectedTicket.ToStationCode! 20 | leftTicket = selectedTicket.yp_info! 21 | } 22 | 23 | var train_date = "" 24 | var train_no = "" 25 | var stationTrainCode = "" 26 | var seatType = "" 27 | var fromStationTelecode = "" 28 | var toStationTelecode = "" 29 | var leftTicket = "" 30 | var purpose_codes = "ADULT" 31 | 32 | 33 | func ToPostParams()->[String:String]{ 34 | return [ 35 | "train_date":train_date, 36 | "train_no":train_date,//2015-11-17 37 | "tour_flag":train_no, 38 | "stationTrainCode":stationTrainCode, 39 | "seatType":seatType, 40 | "fromStationTelecode":fromStationTelecode, 41 | "toStationTelecode":toStationTelecode, 42 | "leftTicket":leftTicket, 43 | "purpose_codes":purpose_codes, 44 | "_json_att":""] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /12306ForMac/Model/GetQueueCountParam.swift: -------------------------------------------------------------------------------- 1 | // 2 | // getQueueCountParam.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/12/14. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct GetQueueCountParam{ 12 | 13 | init(with selectedTicket:QueryLeftNewDTO, seatCode:String, trainLocation:String, globalSubmitToken:String) { 14 | train_date = selectedTicket.jsStartTrainDateStr! 15 | train_no = selectedTicket.train_no! 16 | stationTrainCode = selectedTicket.TrainCode! 17 | seatType = seatCode 18 | fromStationTelecode = selectedTicket.FromStationCode! 19 | toStationTelecode = selectedTicket.ToStationCode! 20 | leftTicket = selectedTicket.yp_info! 21 | train_location = trainLocation 22 | REPEAT_SUBMIT_TOKEN = globalSubmitToken 23 | } 24 | 25 | var train_date = "" 26 | var train_no = "" 27 | var stationTrainCode = "" 28 | var seatType = "" 29 | var fromStationTelecode = "" 30 | var toStationTelecode = "" 31 | var leftTicket = "" 32 | var purpose_codes = "00" 33 | var train_location = "" 34 | 35 | var REPEAT_SUBMIT_TOKEN = "" 36 | 37 | func ToPostParams()->[String:String]{ 38 | return [ 39 | "train_date":train_date, 40 | "train_no":train_date,//2015-11-17 41 | "tour_flag":train_no, 42 | "stationTrainCode":stationTrainCode, 43 | "seatType":seatType, 44 | "fromStationTelecode":fromStationTelecode, 45 | "toStationTelecode":toStationTelecode, 46 | "leftTicket":leftTicket, 47 | "purpose_codes":purpose_codes, 48 | "train_location":train_location, 49 | "_json_att":"", 50 | "REPEAT_SUBMIT_TOKEN":REPEAT_SUBMIT_TOKEN] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /12306ForMac/Model/LeftTicketParam.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftTicketDTO.swift 3 | // Train12306 4 | // 5 | // Created by fancymax on 15/10/24. 6 | // Copyright © 2015年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct LeftTicketParam{ 12 | //2015-09-23 13 | var train_date = "2015-09-23" 14 | //SZQ -> 深圳 15 | var from_stationCode = "SZQ" 16 | //SHH -> 上海 17 | var to_stationCode = "SHH" 18 | //ADULT 19 | var purpose_codes = "ADULT" 20 | 21 | func ToGetParams()->String{ 22 | return "leftTicketDTO.train_date=\(train_date)&leftTicketDTO.from_station=\(from_stationCode)&leftTicketDTO.to_station=\(to_stationCode)&purpose_codes=\(purpose_codes)" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /12306ForMac/Model/MainModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainModel.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 15/10/6. 6 | // Copyright © 2015年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class MainModel{ 12 | static var realName = "" 13 | static var userName = "" 14 | static var isGetUserInfo = false 15 | 16 | static var passengers = [PassengerDTO]() 17 | static var selectPassengers = [PassengerDTO]() 18 | static var isGetPassengersInfo = false 19 | 20 | static var selectedTicket:QueryLeftNewDTO? 21 | 22 | static var orderId:String? 23 | 24 | static var globalRepeatSubmitToken:String = "" 25 | static var key_check_isChange:String? 26 | static var train_location:String? 27 | static var trainDate:String? 28 | 29 | static var historyOrderList:[OrderDTO] = [] 30 | static var noCompleteOrderList:[OrderDTO] = [] 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /12306ForMac/Model/OrderDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OrderDTO.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/2/16. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | class OrderDTO:NSObject{ 13 | var sequence_no: String? 14 | 15 | var start_train_date_page: String? 16 | // var train_code_page: String? 17 | var ticket_total_price_page: String? 18 | var return_flag: String? 19 | var resign_flag: String? 20 | var arrive_time_page: String? 21 | 22 | //tickets[x] 23 | var coach_no: String? //车厢号 05 24 | var seat_name: String? //席位号 01A号 25 | var str_ticket_price_page: String? //票价 603.5 26 | var ticket_type_name: String? //票种 成人票 27 | var seat_type_name: String? //席别 一等座 28 | var ticket_status_code: String? //状态 i 29 | var ticket_status_name: String? //状态 待支付 30 | var pay_limit_time: String? //限定支付时间 2016-06-14 10:36:28 31 | 32 | // stationTrainDTO 33 | var station_train_code: String? //G6012 34 | var from_station_name: String? //深圳北 35 | var to_station_name: String? //长沙南 36 | 37 | //passengerDTO 38 | var passenger_name :String? //小明 39 | 40 | //modified 41 | //05车厢 42 | var coachName:String{ 43 | var name = "" 44 | if let no = coach_no { 45 | name = no + "车厢" 46 | } 47 | return name 48 | } 49 | 50 | var startTrainDate:Date? { 51 | if start_train_date_page == nil { 52 | return nil 53 | } 54 | let dateFormatter = DateFormatter() 55 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm" 56 | if let date = dateFormatter.date(from: start_train_date_page!) { 57 | return date 58 | } 59 | else { 60 | logger.error("trainDateStr2Date dateStr = \(self.start_train_date_page)") 61 | return nil 62 | } 63 | } 64 | 65 | var startEndStation:String { 66 | return "\(from_station_name!)->\(to_station_name!)" 67 | } 68 | 69 | var whereToSeat:String { 70 | return "\(coachName) \(seat_name!)" 71 | } 72 | 73 | //¥603.5 74 | var ticketPrice:String{ 75 | var name = "" 76 | if let price = str_ticket_price_page { 77 | name = "¥" + price 78 | } 79 | return name 80 | } 81 | 82 | var payStatus:String{ 83 | var name = "" 84 | if let limitTime = pay_limit_time, let status = ticket_status_name, let code = ticket_status_code { 85 | if code == "i" { 86 | name = "\(status)(请在 \(limitTime) 前支付)" 87 | } 88 | else { 89 | name = "\(status)" 90 | } 91 | } 92 | return name 93 | } 94 | 95 | init(json:JSON,ticketIdx:Int) 96 | { 97 | sequence_no = json["sequence_no"].string 98 | arrive_time_page = json["arrive_time_page"].string 99 | 100 | start_train_date_page = json["tickets"][ticketIdx]["start_train_date_page"].string 101 | 102 | coach_no = json["tickets"][ticketIdx]["coach_no"].string 103 | seat_name = json["tickets"][ticketIdx]["seat_name"].string 104 | str_ticket_price_page = json["tickets"][ticketIdx]["str_ticket_price_page"].string 105 | ticket_type_name = json["tickets"][ticketIdx]["ticket_type_name"].string 106 | seat_type_name = json["tickets"][ticketIdx]["seat_type_name"].string 107 | ticket_status_name = json["tickets"][ticketIdx]["ticket_status_name"].string 108 | ticket_status_code = json["tickets"][ticketIdx]["ticket_status_code"].string 109 | pay_limit_time = json["tickets"][ticketIdx]["pay_limit_time"].string 110 | 111 | station_train_code = json["tickets"][ticketIdx]["stationTrainDTO"]["station_train_code"].string 112 | from_station_name = json["tickets"][ticketIdx]["stationTrainDTO"]["from_station_name"].string 113 | to_station_name = json["tickets"][ticketIdx]["stationTrainDTO"]["to_station_name"].string 114 | 115 | passenger_name = json["tickets"][ticketIdx]["passengerDTO"]["passenger_name"].string 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /12306ForMac/Model/PassengerDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PassengerDTO.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 15/8/24. 6 | // Copyright (c) 2015年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | enum TicketType:String,CustomStringConvertible { 13 | case Student = "0X00" 14 | case Normal = "ADULT" 15 | 16 | var description: String { 17 | switch self { 18 | case .Student: 19 | return "学生" 20 | case .Normal: 21 | return "成人" 22 | } 23 | } 24 | 25 | var id_type_code:String { 26 | switch self { 27 | case .Student: 28 | return "3" 29 | case .Normal: 30 | return "1" 31 | } 32 | } 33 | } 34 | 35 | class PassengerDTO:NSObject { 36 | 37 | let code :String 38 | let passenger_name :String 39 | var sex_code :String? 40 | var sex_name :String? 41 | var born_date :String? 42 | var country_code :String? 43 | var passenger_id_type_code :String 44 | var passenger_id_type_name :String 45 | let passenger_id_no :String 46 | var passenger_type :String 47 | var passenger_type_name :String? 48 | var passenger_flag :String? 49 | var mobile_no :String? 50 | var phone_no :String? 51 | var email :String? 52 | var address :String? 53 | var postalcode :String? 54 | var first_letter :String? 55 | var recordCount :String? 56 | var total_times :String? 57 | var index_id :String? 58 | 59 | var isChecked: Bool 60 | var seatCode = "O"; 61 | var seatCodeName = "二等座"; 62 | var canSelectTicketType = false 63 | 64 | let passenger_type_name_Dic = ["成人","学生"] 65 | var passenger_id_type_select_index = 0 { 66 | willSet { 67 | if passenger_type_name_Dic[newValue] == TicketType.Student.description { 68 | passenger_type = TicketType.Student.id_type_code 69 | } 70 | else { 71 | passenger_type = TicketType.Normal.id_type_code 72 | } 73 | } 74 | } 75 | 76 | 77 | init(json:JSON) 78 | { 79 | isChecked = false 80 | 81 | code = json["code"].string! 82 | passenger_name = json["passenger_name"].string! 83 | sex_code = json["sex_code"].string 84 | sex_name = json["sex_name"].string 85 | born_date = json["born_date"].string 86 | country_code = json["country_code"].string 87 | passenger_id_type_code = json["passenger_id_type_code"].string! 88 | passenger_id_type_name = json["passenger_id_type_name"].string! 89 | passenger_id_no = json["passenger_id_no"].string! 90 | passenger_type = json["passenger_type"].string! 91 | passenger_flag = json["passenger_flag"].string 92 | passenger_type_name = json["passenger_type_name"].string 93 | mobile_no = json["mobile_no"].string 94 | phone_no = json["phone_no"].string 95 | email = json["email"].string 96 | address = json["address"].string 97 | postalcode = json["postalcode"].string 98 | first_letter = json["first_letter"].string 99 | recordCount = json["recordCount"].string 100 | total_times = json["total_times"].string 101 | index_id = json["index_id"].string 102 | 103 | } 104 | 105 | func setDefaultTicketType(date:Date){ 106 | let sysDate = LunarCalendarView.toUTCDateComponent(date) 107 | let availableMonth = [6,7,8,9,12,1,2,3] 108 | let isDateAvailable:Bool 109 | if (availableMonth.contains(sysDate.month!)) { 110 | isDateAvailable = true 111 | } 112 | else { 113 | isDateAvailable = false 114 | } 115 | 116 | let isSeatAvailable:Bool 117 | if ((seatCode == "O") || (seatCode == "1") || (seatCode == "3")) { 118 | isSeatAvailable = true 119 | } 120 | else { 121 | isSeatAvailable = false 122 | } 123 | 124 | let isStudent:Bool 125 | if passenger_type_name == "学生" { 126 | isStudent = true 127 | } 128 | else { 129 | isStudent = false 130 | } 131 | 132 | if isStudent { 133 | if isDateAvailable && isSeatAvailable { 134 | passenger_id_type_select_index = 1 135 | canSelectTicketType = true 136 | } 137 | else { 138 | passenger_id_type_select_index = 0 139 | canSelectTicketType = false 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /12306ForMac/Model/PaycheckNewParam.swift: -------------------------------------------------------------------------------- 1 | // 2 | // paycheckNewParam.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/11/30. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct PaycheckNewParam { 12 | 13 | func ToPostParams()->[String:String]{ 14 | return [ 15 | "batch_nos":"", 16 | "coach_nos":"", 17 | "seat_nos":"", 18 | "passenger_id_types":"", 19 | "passenger_id_nos":"", 20 | "passenger_names":"", 21 | "insure_price_all":"", 22 | "insure_types":"", 23 | "if_buy_insure_only":"N", 24 | "hasBoughtIns":"", 25 | "json_att":"" 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /12306ForMac/Model/QueryByTrainCodeParam.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryByTrainCodeParam.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/06/12. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct QueryTrainCodeParam { 12 | let _train_no:String //6i000D232806" 13 | let _from_station_telecode:String // "IOQ" 14 | let _to_station_telecode:String //"FYS" 15 | let _depart_date:String //2016-09-10 !!列车出发时间!! 16 | 17 | init(_ ticket:QueryLeftNewDTO) { 18 | self.init(train_no:ticket.train_no,from_station_telecode:ticket.FromStationCode!,to_station_telecode:ticket.ToStationCode!,depart_date:ticket.start_train_date_formatStr!) 19 | } 20 | 21 | init(train_no:String,from_station_telecode:String,to_station_telecode:String,depart_date:String) { 22 | _train_no = train_no 23 | _from_station_telecode = from_station_telecode 24 | _to_station_telecode = to_station_telecode 25 | _depart_date = depart_date 26 | } 27 | 28 | //train_no=6i000D232806&from_station_telecode=IOQ&to_station_telecode=FYS&depart_date=2016-06-12 29 | func ToGetParams()->String{ 30 | return "train_no=\(_train_no)&from_station_telecode=\(_from_station_telecode)&to_station_telecode=\(_to_station_telecode)&depart_date=\(_depart_date)" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /12306ForMac/Model/QueryOrderParam.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryOrderParam.swift 3 | // Train12306 4 | // 5 | // Created by fancymax on 16/2/16. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct QueryOrderParam{ 12 | var queryType = "1" 13 | var queryStartDate = "2016-06-01" 14 | var queryEndDate = "2020-02-15" 15 | var come_from_flag = "my_order" 16 | var pageSize = 8 17 | var pageIndex = 0 18 | var query_where = "G" 19 | var sequeue_train_name = "" 20 | 21 | func ToPostParams()->[String:String]{ 22 | return ["queryType":queryType, 23 | "queryStartDate":queryStartDate, 24 | "queryEndDate":queryEndDate, 25 | "come_from_flag":come_from_flag, 26 | "pageSize":String(pageSize), 27 | "pageIndex":String(pageIndex), 28 | "query_where":query_where, 29 | "sequeue_train_name":sequeue_train_name, 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /12306ForMac/Model/QueryOrderWaitTimeResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryOrderWaitTimeResult.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/3/3. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | class QueryOrderWaitTimeResult{ 13 | var queryOrderWaitTimeStatus: Bool? 14 | var count:Int? 15 | var waitTime:Int? 16 | // var requestId: 17 | var waitCount:Int? 18 | var tourFlag:String? 19 | var orderId:String? 20 | var msg:String? 21 | 22 | init(json:JSON) 23 | { 24 | queryOrderWaitTimeStatus = json["queryOrderWaitTimeStatus"].boolValue 25 | count = json["count"].intValue 26 | waitTime = json["waitTime"].intValue 27 | waitCount = json["waitCount"].intValue 28 | tourFlag = json["tourFlag"].string 29 | orderId = json["orderId"].string 30 | msg = json["msg"].string 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /12306ForMac/Model/QueryTrainPriceParam.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryTrainPriceParam.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/11/8. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct QueryTrainPriceParam { 12 | let _train_no:String // "6i000D232806" 13 | let _from_station_no:String // "01" 14 | let _to_station_no:String // "14" 15 | let _seat_types:String // "OM9" 16 | let _train_date:String // "2016-12-29" 17 | 18 | init(_ ticket:QueryLeftNewDTO) { 19 | self.init(train_no:ticket.train_no,from_station_no:ticket.from_station_no!,to_station_no:ticket.to_station_no!,seat_types:ticket.seat_types!,train_date:ticket.trainDateStr) 20 | } 21 | 22 | 23 | init(train_no:String,from_station_no:String,to_station_no:String,seat_types:String,train_date:String) { 24 | _train_no = train_no 25 | _from_station_no = from_station_no 26 | _to_station_no = to_station_no 27 | _seat_types = seat_types 28 | _train_date = train_date 29 | } 30 | 31 | //?train_no=6i000G160202&from_station_no=01&to_station_no=14&seat_types=OM9&train_date=2016-12-2 32 | func ToGetParams()->String{ 33 | return "train_no=\(_train_no)&from_station_no=\(_from_station_no)&to_station_no=\(_to_station_no)&seat_types=\(_seat_types)&train_date=\(_train_date)" 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /12306ForMac/Model/SubmitOrderParams.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SubmitOrderParams.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/10/31. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct SubmitOrderParams{ 12 | 13 | init(with ticket:QueryLeftNewDTO, purposeCode:String) { 14 | secretStr = ticket.SecretStr! 15 | train_date = ticket.trainDateStr 16 | back_train_date = ticket.trainDateStr 17 | //tour_flag dc 18 | purpose_codes = purposeCode 19 | query_from_station_name = ticket.FromStationName! 20 | query_to_station_name = ticket.ToStationName! 21 | 22 | } 23 | 24 | var secretStr = "" 25 | 26 | var train_date = "" 27 | 28 | var back_train_date = "" 29 | 30 | let tour_flag = "dc" 31 | 32 | var purpose_codes = "ADULT" 33 | 34 | var query_from_station_name = "" 35 | 36 | var query_to_station_name = "" 37 | 38 | func ToPostParams()->[String:String]{ 39 | return [ 40 | "secretStr":secretStr, 41 | "train_date":train_date,//2015-11-17 42 | "back_train_date":back_train_date,//2015-11-03 43 | "tour_flag":tour_flag, 44 | "purpose_codes":purpose_codes, 45 | "query_from_station_name":query_from_station_name, 46 | "query_to_station_name":query_to_station_name, 47 | "undefined":""] 48 | } 49 | } -------------------------------------------------------------------------------- /12306ForMac/Model/TicketConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TicketConstants.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/9/10. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | //let cardTypeNameDic = ["二代身份证": "1", "一代身份证": "2", "港澳通行证": "C", "台湾通行证": "G", "护照": "B"] 12 | 13 | let SEAT_TYPE_ARRAY = ["商务座", "特等座", "一等座", "二等座", "高级软卧", "软卧", "硬卧", "软座", "硬座", "无座"] 14 | 15 | //动车 16 | let D_SEAT_TYPE_NAME_DIC = ["高级软卧":"A","商务座": "9", "特等座": "P", "一等座": "M", "二等座": "O","软卧": "F", "无座": "O"] 17 | let D_SEAT_TYPE_KEYPATH_DIC = ["高级软卧": "Gr_Num","商务座": "Swz_Num", "特等座": "Tz_Num", "一等座": "Zy_Num", "二等座": "Ze_Num","软卧": "Rw_Num", "无座": "Wz_Num"] 18 | 19 | //普通车 20 | let K_SEAT_TYPE_NAME_DIC = ["高级软卧": "6","软卧": "4", "硬卧": "3", "软座": "2", "硬座": "1", "无座": "1"] 21 | let K_SEAT_TYPE_KEYPATH_DIC = ["高级软卧": "Gr_Num","软卧": "Rw_Num", "硬卧": "Yw_Num", "软座": "Rz_Num", "硬座": "Yz_Num", "无座": "Wz_Num"] 22 | 23 | func G_QuerySeatTypeNameDicBy(_ trainCode:String)->[String:String] { 24 | if (trainCode.contains("G"))||(trainCode.contains("D")||(trainCode.contains("C"))) { 25 | return D_SEAT_TYPE_NAME_DIC; 26 | } 27 | else { 28 | return K_SEAT_TYPE_NAME_DIC; 29 | } 30 | } 31 | 32 | func G_QuerySeatTypeKeyPathDicBy(_ trainCode:String)->[String:String] { 33 | if (trainCode.contains("G"))||(trainCode.contains("D")||(trainCode.contains("C"))) { 34 | return D_SEAT_TYPE_KEYPATH_DIC; 35 | } 36 | else { 37 | return K_SEAT_TYPE_KEYPATH_DIC; 38 | } 39 | } 40 | 41 | //20160502->2016-05-02 42 | func G_Convert2StartTrainDateStr(_ dateStr: String)->String{ 43 | var formateStr = dateStr 44 | var index = dateStr.characters.index(dateStr.startIndex, offsetBy: 4) 45 | formateStr.insert("-", at: index) 46 | index = dateStr.characters.index(dateStr.startIndex, offsetBy: 7) 47 | formateStr.insert("-", at: index) 48 | 49 | return formateStr 50 | } 51 | 52 | -------------------------------------------------------------------------------- /12306ForMac/Model/TicketQueueCountResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TicketQueueCountResult.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/3/3. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | class TicketQueueCountResult{ 13 | var count:String? 14 | var countT:String? 15 | var op_2:String? 16 | var op_1:String? 17 | var ticket:String? 18 | var isRelogin:String? 19 | 20 | 21 | init(json:JSON) 22 | { 23 | count = json["count"].string 24 | countT = json["countT"].string 25 | op_1 = json["op_1"].string 26 | op_2 = json["op_2"].string 27 | ticket = json["ticket"].string 28 | isRelogin = json["isRelogin"].string 29 | } 30 | 31 | func shouldRelogin()->Bool { 32 | if let reloginStr = isRelogin , reloginStr == "Y" { 33 | return true; 34 | } 35 | else { 36 | return false; 37 | } 38 | } 39 | 40 | func isTicketSoldOut() -> Bool { 41 | if let status = op_2 , status == "true" { 42 | return true; 43 | } 44 | else { 45 | return false; 46 | } 47 | } 48 | 49 | func getWarningInfoBy(_ seatCodeName:String) -> String { 50 | var warningStr = "" 51 | if let leftTicket = ticket { 52 | if leftTicket.contains(",") { 53 | let nums = leftTicket.components(separatedBy: ",") 54 | assert(nums.count == 2) 55 | warningStr += "本次列车 剩余\(seatCodeName) \(nums[0]) 张, 无座 \(nums[1])张" 56 | } 57 | else { 58 | warningStr += "本次列车 剩余\(seatCodeName) \(leftTicket) 张" 59 | } 60 | } 61 | if let status = op_2 , status == "true" { 62 | warningStr += ",目前排队人数已经超过余票张数,请您选择其他席别或车次" 63 | } 64 | else { 65 | if let queueNum = countT , queueNum != "0" { 66 | warningStr += ",目前排队人数 \(queueNum)" 67 | } 68 | } 69 | return warningStr 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /12306ForMac/Model/TrainCodeDetail.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrainCodeDetail.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/06/12. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | import SwiftyJSON 12 | 13 | class TrainCodeDetail: NSObject { 14 | var station_no: String! // = "1" 15 | var station_name: String! // = "深圳北" 16 | var arrive_time: String! // = "-----" 17 | var start_time: String! // = "16:07" 18 | var stopover_time: String! // = "----" 19 | var isEnable: Bool! // = True 20 | var textColor:NSColor! 21 | 22 | init(json:JSON) { 23 | station_no = json["station_no"].string 24 | station_name = json["station_name"].string 25 | arrive_time = json["arrive_time"].string 26 | start_time = json["start_time"].string 27 | stopover_time = json["stopover_time"].string 28 | isEnable = json["isEnabled"].boolValue 29 | if isEnable! { 30 | textColor = NSColor.black 31 | } 32 | else{ 33 | textColor = NSColor.gray 34 | } 35 | } 36 | 37 | } 38 | 39 | class TrainCodeDetails: NSObject { 40 | var start_station_name: String! // = "深圳北" 41 | var station_train_code: String! // = "D2306" 42 | var train_class_name: String! // = "动车" 43 | var service_type: String! // = "1" 44 | var end_station_name: String! // = "福州南" 45 | 46 | var trainNos: [TrainCodeDetail]! 47 | 48 | init(json:JSON) { 49 | if json.count <= 0 { 50 | return 51 | } 52 | 53 | start_station_name = json[0]["start_station_name"].string 54 | station_train_code = json[0]["station_train_code"].string 55 | train_class_name = json[0]["train_class_name"].string 56 | service_type = json[0]["service_type"].string 57 | end_station_name = json[0]["end_station_name"].string 58 | 59 | self.trainNos = [TrainCodeDetail]() 60 | for i in 0.. () in 33 | self.stopLoadingTip() 34 | self.showTip(translate(error)) 35 | } 36 | 37 | self.startLoadingTip("正在加载...") 38 | 39 | Service.sharedInstance.payFlow(success: successHandler, failure: failureHandler) 40 | } 41 | 42 | @IBAction func clickRefreshPay(_ button:NSButton) { 43 | self.refreshPay() 44 | } 45 | 46 | @IBAction func clickCancel(_ button:NSButton){ 47 | dismissWithModalResponse(NSModalResponseOK) 48 | } 49 | 50 | @IBAction func open12306(_ sender: NSButton) { 51 | NSWorkspace.shared().open(URL(string: "https://kyfw.12306.cn/otn/login/init")!) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /12306ForMac/Preferences/AdvancedPreferenceManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdvancedPrefereceManager.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/8/10. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class AdvancedPreferenceManager { 12 | 13 | static let sharedInstance = AdvancedPreferenceManager() 14 | 15 | fileprivate let isUseDamaKey = "isUseDama" 16 | fileprivate let isUseDamaLoginKey = "isUseDamaLogin" 17 | fileprivate let isStopDamaWhenOperateKey = "isStopDamaWhenOperate" 18 | fileprivate let isStopDamaWhenFail5Key = "isStopDamaWhenFail5" 19 | 20 | fileprivate let damaUserKey = "damaUser" 21 | fileprivate let damaPasswordKey = "damaPassword" 22 | 23 | fileprivate let userDefaults = UserDefaults.standard 24 | fileprivate init() 25 | { 26 | registerUserDefault() 27 | } 28 | 29 | fileprivate func registerUserDefault() 30 | { 31 | let firstDefault = [isUseDamaKey: false,isUseDamaLoginKey: true,isStopDamaWhenOperateKey:true,isStopDamaWhenFail5Key: true,damaUserKey:"",damaPasswordKey:""] as [String : Any] 32 | userDefaults.register(defaults: firstDefault) 33 | } 34 | 35 | var isUseDama:Bool { 36 | get{ 37 | return userDefaults.object(forKey: isUseDamaKey) as! Bool 38 | } 39 | set{ 40 | userDefaults.set(newValue, forKey: isUseDamaKey) 41 | } 42 | } 43 | 44 | var isUseDamaLogin:Bool { 45 | get{ 46 | return userDefaults.object(forKey: isUseDamaLoginKey) as! Bool 47 | } 48 | set{ 49 | userDefaults.set(newValue, forKey: isUseDamaLoginKey) 50 | } 51 | } 52 | 53 | var isStopDamaWhenOperate:Bool { 54 | get{ 55 | return userDefaults.object(forKey: isStopDamaWhenOperateKey) as! Bool 56 | } 57 | set{ 58 | userDefaults.set(newValue, forKey: isStopDamaWhenOperateKey) 59 | } 60 | } 61 | 62 | var isStopDamaWhenFail5:Bool { 63 | get{ 64 | return userDefaults.object(forKey: isStopDamaWhenFail5Key) as! Bool 65 | } 66 | set{ 67 | userDefaults.set(newValue, forKey: isStopDamaWhenFail5Key) 68 | 69 | } 70 | } 71 | 72 | var damaUser:String { 73 | get{ 74 | return userDefaults.object(forKey: damaUserKey) as! String 75 | } 76 | set{ 77 | userDefaults.set(newValue, forKey: damaUserKey) 78 | } 79 | } 80 | 81 | var damaPassword:String{ 82 | get{ 83 | return userDefaults.object(forKey: damaPasswordKey) as! String 84 | } 85 | set{ 86 | userDefaults.set(newValue, forKey: damaPasswordKey) 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /12306ForMac/Preferences/AdvancedPreferenceViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdvancePreferenceViewController.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/8/9. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import MASPreferences 11 | 12 | class AdvancedPreferenceViewController: NSViewController,MASPreferencesViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | // Do view setup here. 17 | if !isUseDama { 18 | logoutDama() 19 | } 20 | else { 21 | getBalance() 22 | } 23 | } 24 | 25 | override var nibName: String? { 26 | return "AdvancedPreferenceViewController" 27 | } 28 | 29 | // MARK: - MASPreferencesViewController 30 | override var identifier: String!{ 31 | get { 32 | return "AdvancedPreferences" 33 | } 34 | set{} 35 | } 36 | 37 | var toolbarItemImage: NSImage! { 38 | return NSImage(named: NSImageNameUser) 39 | } 40 | 41 | var toolbarItemLabel: String! { 42 | return NSLocalizedString("打码兔", comment: "") 43 | } 44 | 45 | // MARK: - UserDefault 46 | var isUseDama:Bool { 47 | get { 48 | return AdvancedPreferenceManager.sharedInstance.isUseDama 49 | } 50 | set { 51 | AdvancedPreferenceManager.sharedInstance.isUseDama = newValue 52 | logger.info("dama = \(newValue)") 53 | if newValue { 54 | getBalance() 55 | NotificationCenter.default.post(name: Notification.Name.App.DidDamaGetBalance, object:true) 56 | } 57 | else { 58 | NotificationCenter.default.post(name: Notification.Name.App.DidDamaGetBalance, object:false) 59 | logoutDama() 60 | } 61 | } 62 | } 63 | 64 | var isUseDamaLogin:Bool { 65 | get{ 66 | return AdvancedPreferenceManager.sharedInstance.isUseDamaLogin 67 | } 68 | set{ 69 | AdvancedPreferenceManager.sharedInstance.isUseDamaLogin = newValue 70 | } 71 | } 72 | 73 | var isStopDamaWhenOperate:Bool { 74 | get{ 75 | return AdvancedPreferenceManager.sharedInstance.isStopDamaWhenOperate 76 | } 77 | set{ 78 | AdvancedPreferenceManager.sharedInstance.isStopDamaWhenOperate = newValue 79 | } 80 | } 81 | 82 | var isStopDamaWhenFail5:Bool { 83 | get{ 84 | return AdvancedPreferenceManager.sharedInstance.isStopDamaWhenFail5 85 | } 86 | set{ 87 | AdvancedPreferenceManager.sharedInstance.isStopDamaWhenFail5 = newValue 88 | 89 | } 90 | } 91 | 92 | var damaUser:String { 93 | get{ 94 | return AdvancedPreferenceManager.sharedInstance.damaUser 95 | } 96 | set{ 97 | AdvancedPreferenceManager.sharedInstance.damaUser = newValue 98 | } 99 | } 100 | 101 | var damaPassword:String{ 102 | get{ 103 | return AdvancedPreferenceManager.sharedInstance.damaPassword 104 | } 105 | set{ 106 | AdvancedPreferenceManager.sharedInstance.damaPassword = newValue 107 | } 108 | } 109 | // MARK: - Control and action 110 | @IBOutlet weak var damaUserlbl: NSTextField! 111 | @IBOutlet weak var damaPasswordlbl: NSSecureTextField! 112 | @IBOutlet weak var balancelbl: NSTextField! 113 | @IBAction func clickGetBalanceOfDama(_ sender: AnyObject) { 114 | getBalance() 115 | } 116 | 117 | func getBalance() { 118 | let successHandler = { (balance:String) ->() in 119 | self.balancelbl.stringValue = "已登录:当前题分 \(balance)" 120 | } 121 | 122 | let failureHandler = { (error:NSError)->() in 123 | self.balancelbl.stringValue = "登录失败: \(translate(error))" 124 | } 125 | 126 | Dama.sharedInstance.getBalance(damaUser, password: damaPassword, success: successHandler, failure: failureHandler) 127 | } 128 | 129 | func logoutDama() { 130 | self.balancelbl.stringValue = "未登录" 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /12306ForMac/Preferences/FilterPreferenceViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // filterPreferenceViewController.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2017/4/2. 6 | // Copyright © 2017年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import MASPreferences 11 | 12 | class FilterTimeSpan:NSObject { 13 | var start:String = "" 14 | var end:String = "" 15 | var isCheck = true 16 | 17 | override var debugDescription: String { 18 | return "\(start)~\(end) \(isCheck)" 19 | } 20 | } 21 | 22 | class FilterPreferenceViewController: NSViewController { 23 | 24 | override var nibName: String? { 25 | return "FilterPreferenceViewController" 26 | } 27 | 28 | override var identifier: String!{ 29 | get { 30 | return "FilterPreferences" 31 | } 32 | set { 33 | super.identifier = newValue 34 | } 35 | } 36 | 37 | var toolbarItemImage: NSImage! { 38 | return NSImage(named: "DefineFilter") 39 | } 40 | 41 | var toolbarItemLabel: String! { 42 | return NSLocalizedString("筛选", comment: "Filter") 43 | } 44 | 45 | @IBOutlet weak var startFilterTimeSpanTable: NSTableView! 46 | @IBOutlet weak var startTimeSpanComboBox: NSComboBox! 47 | 48 | @IBOutlet weak var endFilterTimeSpanTable: NSTableView! 49 | @IBOutlet weak var endTimeSpanComboBox: NSComboBox! 50 | 51 | override func viewDidLoad() { 52 | super.viewDidLoad() 53 | self.initStartFilterTimeSpans() 54 | self.initEndFilterTimeSpans() 55 | } 56 | 57 | var startFilterTimeSpans:[FilterTimeSpan] = [FilterTimeSpan]() 58 | private func initStartFilterTimeSpans() { 59 | var timeSpan = GeneralPreferenceManager.sharedInstance.userDefindStartFilterTimeSpan 60 | var timeStatus = GeneralPreferenceManager.sharedInstance.userDefindStartFilterTimeStatus 61 | 62 | startFilterTimeSpans = [FilterTimeSpan]() 63 | 64 | let count = min(timeSpan.count, timeStatus.count) 65 | 66 | for i in 0.. Int { 165 | if tableView == startFilterTimeSpanTable { 166 | return startFilterTimeSpans.count 167 | } 168 | else { 169 | return endFilterTimeSpans.count 170 | } 171 | } 172 | 173 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { 174 | if tableView == startFilterTimeSpanTable { 175 | return startFilterTimeSpans[row] 176 | } 177 | else { 178 | return endFilterTimeSpans[row] 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /12306ForMac/Preferences/GeneralPreferenceManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeneralPreferenceManager.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/8/10. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class GeneralPreferenceManager { 12 | 13 | static let sharedInstance = GeneralPreferenceManager() 14 | 15 | fileprivate let autoQuerySecondsKey = "autoQuerySeconds" 16 | fileprivate let isShowInvalidTicketKey = "isShowInvalidTicket" 17 | fileprivate let isShowNoTrainTicketKey = "isShowNoTrainTicket" 18 | fileprivate let isNotifyTicketKey = "isNotifyTicket" 19 | fileprivate let isNotifyLoginKey = "isNotifyLogin" 20 | fileprivate let notifyStrKey = "notifyStr" 21 | fileprivate let notifyLoginStrKey = "notifyLoginStr" 22 | fileprivate let isAutoQueryAfterFilterKey = "isAutoQueryAfterFilter" 23 | fileprivate let userDefindStartFilterTimeSpanKey = "userDefindStartFilterTimeSpan" 24 | fileprivate let userDefindEndFilterTimeSpanKey = "userDefindEndFilterTimeSpan" 25 | fileprivate let userDefindStartFilterTimeStatusKey = "userDefindStartFilterTimeStatus" 26 | fileprivate let userDefindEndFilterTimeStatusKey = "userDefindEndFilterTimeStatus" 27 | 28 | fileprivate let userDefaults = UserDefaults.standard 29 | 30 | fileprivate init() 31 | { 32 | registerUserDefault() 33 | } 34 | 35 | fileprivate func registerUserDefault() 36 | { 37 | let firstDefault = [autoQuerySecondsKey: 5, 38 | isShowInvalidTicketKey: true, 39 | isShowNoTrainTicketKey:true, 40 | isNotifyTicketKey:true, 41 | notifyStrKey:"订到票啦", 42 | isNotifyLoginKey:true, 43 | notifyLoginStrKey:"要登录啦", 44 | isAutoQueryAfterFilterKey:true, 45 | userDefindStartFilterTimeSpanKey:["00:00~06:00","06:00~12:00","12:00~18:00","18:00~24:00"], 46 | userDefindEndFilterTimeSpanKey:["00:00~06:00","06:00~12:00","12:00~18:00","18:00~24:00"], 47 | userDefindStartFilterTimeStatusKey: [true,true,true,true], 48 | userDefindEndFilterTimeStatusKey: [true,true,true,true], 49 | ] 50 | as [String : Any] 51 | userDefaults.register(defaults: firstDefault) 52 | } 53 | 54 | var autoQuerySeconds:Int { 55 | get{ 56 | return userDefaults.object(forKey: autoQuerySecondsKey) as! Int 57 | } 58 | set{ 59 | userDefaults.set(newValue, forKey: autoQuerySecondsKey) 60 | } 61 | } 62 | 63 | var isShowInvalidTicket:Bool { 64 | get{ 65 | return userDefaults.object(forKey: isShowInvalidTicketKey) as! Bool 66 | } 67 | set{ 68 | userDefaults.set(newValue, forKey: isShowInvalidTicketKey) 69 | } 70 | } 71 | 72 | var isShowNoTrainTicket:Bool { 73 | get{ 74 | return userDefaults.object(forKey: isShowNoTrainTicketKey) as! Bool 75 | } 76 | set{ 77 | userDefaults.set(newValue, forKey: isShowNoTrainTicketKey) 78 | } 79 | } 80 | 81 | var isNotifyTicket:Bool { 82 | get{ 83 | return userDefaults.object(forKey: isNotifyTicketKey) as! Bool 84 | } 85 | set{ 86 | userDefaults.set(newValue, forKey: isNotifyTicketKey) 87 | } 88 | } 89 | 90 | var notifyStr:String { 91 | get{ 92 | return userDefaults.object(forKey: notifyStrKey) as! String 93 | } 94 | set{ 95 | userDefaults.set(newValue, forKey: notifyStrKey) 96 | } 97 | } 98 | 99 | var isNotifyLogin:Bool { 100 | get{ 101 | return userDefaults.object(forKey: isNotifyLoginKey) as! Bool 102 | } 103 | set{ 104 | userDefaults.set(newValue, forKey: isNotifyLoginKey) 105 | } 106 | } 107 | 108 | var notifyLoginStr:String { 109 | get{ 110 | return userDefaults.object(forKey: notifyLoginStrKey) as! String 111 | } 112 | set{ 113 | userDefaults.set(newValue, forKey: notifyLoginStrKey) 114 | } 115 | } 116 | 117 | var isAutoQueryAfterFilter:Bool { 118 | get{ 119 | return userDefaults.object(forKey: isAutoQueryAfterFilterKey) as! Bool 120 | } 121 | set{ 122 | userDefaults.set(newValue, forKey: isAutoQueryAfterFilterKey) 123 | } 124 | } 125 | 126 | var userDefindStartFilterTimeSpan:[String] { 127 | get{ 128 | return userDefaults.array(forKey: userDefindStartFilterTimeSpanKey) as! [String] 129 | } 130 | set{ 131 | userDefaults.set(newValue, forKey: userDefindStartFilterTimeSpanKey) 132 | } 133 | } 134 | 135 | var userDefindEndFilterTimeSpan:[String] { 136 | get{ 137 | return userDefaults.array(forKey: userDefindEndFilterTimeSpanKey) as! [String] 138 | } 139 | set{ 140 | userDefaults.set(newValue, forKey: userDefindEndFilterTimeSpanKey) 141 | } 142 | } 143 | 144 | var userDefindStartFilterTimeStatus:[Bool] { 145 | get{ 146 | return userDefaults.array(forKey: userDefindStartFilterTimeStatusKey) as! [Bool] 147 | } 148 | set{ 149 | userDefaults.set(newValue, forKey: userDefindStartFilterTimeStatusKey) 150 | } 151 | } 152 | 153 | var userDefindEndFilterTimeStatus:[Bool] { 154 | get{ 155 | return userDefaults.array(forKey: userDefindEndFilterTimeStatusKey) as! [Bool] 156 | } 157 | set{ 158 | userDefaults.set(newValue, forKey: userDefindEndFilterTimeStatusKey) 159 | } 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /12306ForMac/Preferences/GeneralPreferenceViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeneralPreferenceViewController.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/8/9. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import MASPreferences 11 | 12 | class GeneralPreferenceViewController: NSViewController, MASPreferencesViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | } 17 | 18 | override var nibName: String? { 19 | return "GeneralPreferenceViewController" 20 | } 21 | 22 | override var identifier: String!{ 23 | get { 24 | return "GeneralPreferences" 25 | } 26 | set { 27 | super.identifier = newValue 28 | } 29 | } 30 | 31 | var toolbarItemImage: NSImage! { 32 | return NSImage(named: NSImageNamePreferencesGeneral) 33 | } 34 | 35 | var toolbarItemLabel: String! { 36 | return NSLocalizedString("通用", comment: "General") 37 | } 38 | 39 | var autoQuerySeconds: Int { 40 | get{ 41 | return GeneralPreferenceManager.sharedInstance.autoQuerySeconds 42 | } 43 | set{ 44 | GeneralPreferenceManager.sharedInstance.autoQuerySeconds = newValue 45 | } 46 | } 47 | 48 | var isShowNoTrainTicket: Bool { 49 | get{ 50 | return GeneralPreferenceManager.sharedInstance.isShowNoTrainTicket 51 | } 52 | set{ 53 | GeneralPreferenceManager.sharedInstance.isShowNoTrainTicket = newValue 54 | if !newValue { 55 | willChangeValue(forKey: "isShowInvalidTicket") 56 | isShowInvalidTicket = false 57 | didChangeValue(forKey: "isShowInvalidTicket") 58 | } 59 | else { 60 | NotificationCenter.default.post(name: Notification.Name.App.DidRefilterQueryTicket, object:nil) 61 | } 62 | } 63 | } 64 | 65 | var isShowInvalidTicket: Bool { 66 | get{ 67 | return GeneralPreferenceManager.sharedInstance.isShowInvalidTicket 68 | } 69 | set{ 70 | GeneralPreferenceManager.sharedInstance.isShowInvalidTicket = newValue 71 | if newValue { 72 | willChangeValue(forKey: "isShowNoTrainTicket") 73 | isShowNoTrainTicket = true 74 | didChangeValue(forKey: "isShowNoTrainTicket") 75 | } 76 | else { 77 | NotificationCenter.default.post(name: Notification.Name.App.DidRefilterQueryTicket, object:nil) 78 | } 79 | } 80 | } 81 | 82 | var isAutoQueryAfterFilter: Bool { 83 | get{ 84 | return GeneralPreferenceManager.sharedInstance.isAutoQueryAfterFilter 85 | } 86 | set{ 87 | GeneralPreferenceManager.sharedInstance.isAutoQueryAfterFilter = newValue 88 | } 89 | } 90 | 91 | var isNotifyTicket: Bool { 92 | get{ 93 | return GeneralPreferenceManager.sharedInstance.isNotifyTicket 94 | } 95 | set{ 96 | GeneralPreferenceManager.sharedInstance.isNotifyTicket = newValue 97 | NotifySpeaker.sharedInstance.notify() 98 | } 99 | } 100 | 101 | var notifyStr: String { 102 | get{ 103 | return GeneralPreferenceManager.sharedInstance.notifyStr 104 | } 105 | set{ 106 | GeneralPreferenceManager.sharedInstance.notifyStr = newValue 107 | } 108 | } 109 | 110 | var isNotifyLogin: Bool { 111 | get{ 112 | return GeneralPreferenceManager.sharedInstance.isNotifyLogin 113 | } 114 | set{ 115 | GeneralPreferenceManager.sharedInstance.isNotifyLogin = newValue 116 | NotifySpeaker.sharedInstance.notifyLogin() 117 | } 118 | } 119 | 120 | var notifyLoginStr: String { 121 | get{ 122 | return GeneralPreferenceManager.sharedInstance.notifyLoginStr 123 | } 124 | set{ 125 | GeneralPreferenceManager.sharedInstance.notifyLoginStr = newValue 126 | } 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /12306ForMac/RealmModel/DataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataManager.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/10/9. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import FMDB 11 | 12 | let DATA_DIRECTORY = "\(NSHomeDirectory())/Library/Application Support/\(Bundle.main.bundleIdentifier!)" 13 | let DATA_PATH = "\(DATA_DIRECTORY)/12306ForMac.db" 14 | 15 | class DataManger { 16 | static let sharedInstance = DataManger() 17 | 18 | fileprivate init() { 19 | let isExistDirectory:Bool = FileManager.default.fileExists(atPath: DATA_DIRECTORY, isDirectory: nil) 20 | if !isExistDirectory { 21 | do{ 22 | try FileManager.default.createDirectory(atPath: DATA_DIRECTORY, withIntermediateDirectories: true, attributes: nil) 23 | } 24 | catch { 25 | logger.error("Creat \(DATA_DIRECTORY) fail") 26 | } 27 | } 28 | } 29 | 30 | func queryAllUsers() -> [UserX] { 31 | var users=[UserX]() 32 | let db = FMDatabase(path: DATA_PATH) 33 | 34 | if !(db?.open())! { 35 | logger.error("Could not open db") 36 | return users 37 | } 38 | 39 | db?.executeStatements("create table if not exists 'User' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,user varchar, password varchar)") 40 | 41 | let rs = try! db?.executeQuery("select * from User", values: nil) 42 | //if rs.counto 43 | while(rs?.next())!{ 44 | let newUser = UserX() 45 | newUser.id = (rs?.long(forColumn: "id"))! 46 | newUser.name = (rs?.string(forColumn: "user"))! 47 | newUser.password = (rs?.string(forColumn: "password"))! 48 | 49 | users.append(newUser) 50 | } 51 | db?.close() 52 | 53 | return users 54 | } 55 | 56 | func updateUser(_ newUser:UserX) { 57 | let db = FMDatabase(path: DATA_PATH) 58 | 59 | if !(db?.open())! { 60 | logger.error("Could not open db") 61 | return 62 | } 63 | 64 | do { 65 | try db?.executeUpdate("update User set password=? where id=?", values: [newUser.password, newUser.id]) 66 | } 67 | catch{ 68 | logger.error("updateUser error:\(error)") 69 | } 70 | 71 | defer{ 72 | db?.close() 73 | } 74 | } 75 | 76 | func inserUser(_ newUser:UserX) { 77 | let db = FMDatabase(path: DATA_PATH) 78 | 79 | if !(db?.open())! { 80 | logger.error("Could not open db") 81 | return 82 | } 83 | 84 | do { 85 | try db?.executeUpdate("insert into User (user,password) values (?, ?)", values: [newUser.name, newUser.password]) 86 | } 87 | catch { 88 | logger.error("insertUser error:\(error)") 89 | } 90 | 91 | defer{ 92 | print("db close") 93 | db?.close() 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /12306ForMac/RealmModel/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Person.swift 3 | // Train12306 4 | // 5 | // Created by fancymax on 16/2/19. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | class UserX { 10 | var id:Int = -1 11 | var name = "" 12 | var password = "" 13 | } 14 | -------------------------------------------------------------------------------- /12306ForMac/Resources/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg936\cocoartf1404\cocoasubrtf130 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;\red0\green2\blue255;} 4 | \paperw11900\paperh16840\vieww17700\viewh10020\viewkind0 5 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 6 | 7 | \f0\b\fs24 \cf0 Engineering & Design\ 8 | 9 | \b0 fancymax\ 10 | {\field{\*\fldinst{HYPERLINK "http://fancywt.cn"}}{\fldrslt http://fancywt.cn}}\ 11 | } -------------------------------------------------------------------------------- /12306ForMac/Service/Dama.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dama.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/8/10. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | import SwiftyJSON 12 | 13 | fileprivate func md5(_ string: String) -> String { 14 | let context = UnsafeMutablePointer.allocate(capacity: 1) 15 | var digest = Array(repeating:0, count:Int(CC_MD5_DIGEST_LENGTH)) 16 | CC_MD5_Init(context) 17 | CC_MD5_Update(context, string, CC_LONG(string.lengthOfBytes(using: String.Encoding.utf8))) 18 | CC_MD5_Final(&digest, context) 19 | context.deallocate(capacity: 1) 20 | var hexString = "" 21 | for byte in digest { 22 | hexString += String(format:"%02x", byte) 23 | } 24 | 25 | return hexString 26 | } 27 | 28 | extension Data { 29 | func hexedString() -> String { 30 | return reduce("") {$0 + String(format: "%02x", $1)} 31 | } 32 | 33 | func MD5() -> Data { 34 | var result = Data(count: Int(CC_MD5_DIGEST_LENGTH)) 35 | _ = result.withUnsafeMutableBytes {resultPtr in 36 | self.withUnsafeBytes {(bytes: UnsafePointer) in 37 | CC_MD5(bytes, CC_LONG(count), resultPtr) 38 | } 39 | } 40 | return result 41 | } 42 | } 43 | 44 | class Dama: NSObject { 45 | 46 | static let sharedInstance = Dama() 47 | 48 | let AppId:String = "43327" 49 | let AppKey:String = "36f3ff0b2f66f2b3f1cd9b5953095858" 50 | 51 | private override init() { 52 | super.init() 53 | } 54 | 55 | private func getCurrentFileHex(ofImage image:NSImage)->String { 56 | let originData = image.tiffRepresentation 57 | let imageRep = NSBitmapImageRep(data: originData!) 58 | let imageData = imageRep!.representation(using: .PNG, properties: ["NSImageCompressionFactor":1.0])! 59 | 60 | let result = imageData.hexedString() 61 | return result 62 | } 63 | 64 | private func getpwd(_ user:String,password:String) -> String{ 65 | let nameMD5 = md5(user) 66 | let passwordMD5 = md5(password) 67 | let x1MD5 = md5(nameMD5 + passwordMD5) 68 | let x2MD5 = md5(AppKey + x1MD5) 69 | return x2MD5 70 | } 71 | 72 | private func getsign(ofUser user:String) ->String { 73 | let key = AppKey + user 74 | let x1MD5 = md5(key) 75 | let x2 = x1MD5[x1MD5.startIndex...x1MD5.index(x1MD5.startIndex, offsetBy: 7)] 76 | return x2 77 | } 78 | 79 | private func getFileDataSign2(ofImage image:NSImage,user:String)->String{ 80 | let originData = image.tiffRepresentation 81 | let imageRep = NSBitmapImageRep(data: originData!) 82 | let imageData = imageRep!.representation(using: .PNG, properties: ["NSImageCompressionFactor":1.0])! 83 | 84 | let AppKeyData = AppKey.data(using: String.Encoding.utf8) 85 | let AppUserData = user.data(using: String.Encoding.utf8) 86 | 87 | var finalData = Data() 88 | finalData.append(AppKeyData!) 89 | finalData.append(AppUserData!) 90 | finalData.append(imageData) 91 | 92 | let x1MD5 = finalData.MD5().hexedString() 93 | let startIndex = x1MD5.startIndex 94 | return x1MD5[startIndex...x1MD5.index(startIndex, offsetBy: 7)] 95 | } 96 | 97 | func getBalance(_ user:String,password:String,success:@escaping (String)->Void,failure:@escaping (NSError)->Void){ 98 | 99 | let url = "http://api.dama2.com:7766/app/d2Balance" 100 | let pwd = getpwd(user,password: password) 101 | let sign = getsign(ofUser: user) 102 | 103 | let urlX = "\(url)?appID=\(AppId)&user=\(user)&pwd=\(pwd)&sign=\(sign)" 104 | Alamofire.request(urlX).responseJSON { response in 105 | switch(response.result){ 106 | case .failure(let error): 107 | failure(error as NSError) 108 | case .success(let data): 109 | if let balanceVal = JSON(data)["balance"].string { 110 | success(balanceVal) 111 | } 112 | else { 113 | if let errorCode = JSON(data)["ret"].int { 114 | failure(DamaError.errorWithCode(errorCode)) 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | func dama(_ user:String,password:String,ofImage image:NSImage,success:@escaping (String)->Void,failure:@escaping (NSError)->Void){ 122 | 123 | let pwd = getpwd(user,password: password) 124 | let sign = getFileDataSign2(ofImage: image,user: user) 125 | let type = "287" 126 | let fileData = getCurrentFileHex(ofImage: image) 127 | let url = "http://api.dama2.com:7766/app/d2File" 128 | 129 | let params = ["appID":AppId, 130 | "user":user, 131 | "pwd":pwd, 132 | "type":type, 133 | "fileData":fileData, 134 | "sign":sign] 135 | Alamofire.request(url, method:.post, parameters: params).responseJSON { response in 136 | switch(response.result) { 137 | case .failure(let error): 138 | failure(error as NSError) 139 | case .success(let data): 140 | if let imageCode = JSON(data)["result"].string { 141 | success(imageCode) 142 | } 143 | else { 144 | if let errorCode = JSON(data)["ret"].int { 145 | failure(DamaError.errorWithCode(errorCode)) 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /12306ForMac/Service/DamaError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DamaError.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/8/13. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct DamaError { 12 | static let Domain = "com.Dama2.error" 13 | 14 | static let errorDic = [ 15 | -1: "系统错误", 16 | -100: "无效软件KEY", 17 | -101: "黑名单机器信息", 18 | -102: "黑名单IP", 19 | -103: "用户不存在", 20 | -104: "用户密码错", 21 | -105: "用户被停用", 22 | -106: "无效服务器类型", 23 | -107: "无效用户类型", 24 | -108: "动态码校验失败,需要发送动态码", 25 | -109: "无效的黑名单类型", 26 | -110: "参数为空", 27 | -111: "验证码字符类型校验错误", 28 | -112: "APPCODETYPEID不存在", 29 | -113: "CODETYPEID不存在", 30 | -114: "无效充值卡密", 31 | -115: "无效的验证码结果状态", 32 | -116: "无效的提现模式", 33 | -117: "无效软件类型", 34 | -118: "无效动态验证码发送类型", 35 | -119: "打码工人选择了错误的字符类型", 36 | -200: "无负载均衡服务器", 37 | -201: "均衡服务器未配置", 38 | -202: "没有配置全客户端类型的版本", 39 | -203: "没有可用验证码", 40 | -204: "提交验证码结果时,没有指定的验证码", 41 | -205: "没有打码工人统计数据", 42 | -206: "没有指定的验证码ID记录", 43 | -300: "COOKIE长度溢出", 44 | -301: "用户名重复", 45 | -302: "报告验证码结果状态失败(没更新到数据)", 46 | -303: "验证码尚未打码完成", 47 | -304: "余额不足,题分不足", 48 | -305: "验证码状态不允许", 49 | -306: "用户动态码发送配置不正确", 50 | -400: "动态验证码发送超限", 51 | -401: "动态码超时", 52 | -402: "功能不支持", 53 | -403: "发送动态验证码失败", 54 | -404: "参数非法", 55 | -406: "用户无权使用该APP", 56 | -9990: "decrypt password error", 57 | -9991: "appID error", 58 | -9992: "用户不存在", 59 | -9993: "用户密码错", 60 | -9994: "no file", 61 | -9995: "id error", 62 | -9996: "email error", 63 | -9997: "tel error", 64 | -9998: "db name error", 65 | -9999: "encrypt key error", 66 | -10000: "busy", 67 | -10020: "program error", 68 | -10021: "db error", 69 | -10022: "db execute error", 70 | -10023: "URL错误", 71 | -10025: "POST数据中包含了多个文件", 72 | -10027: "sign error", 73 | -10028: "extName error", 74 | -10029: "fileData error/fileDataBase64 error"] 75 | 76 | static func errorWithCode(_ code:Int)->NSError{ 77 | if errorDic.keys.contains(code) { 78 | return errorWithCode(code, failureReason: "打码兔错误: \(errorDic[code]!)") 79 | } 80 | else { 81 | return errorWithCode(code, failureReason: "未知错误, 错误码 = \(code)") 82 | } 83 | } 84 | 85 | static func errorWithCode(_ code: Int, failureReason: String) -> NSError { 86 | let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason] 87 | return NSError(domain: Domain, code: code, userInfo: userInfo) 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /12306ForMac/Service/Service+Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Service+Utilities.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/3/19. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | extension Service { 13 | func getWanIP(success:(wanIP:String)->()){ 14 | let url = "http://ifconfig.me/ip" 15 | Service.Manager.request(.GET, url).responseString(completionHandler:{ response in 16 | switch (response.result){ 17 | case .Failure(let error): 18 | print(error) 19 | case .Success(let content): 20 | print(content) 21 | success(wanIP: content) 22 | }})} 23 | } -------------------------------------------------------------------------------- /12306ForMac/Service/Service.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceBase.swift 3 | // Train12306 4 | // 5 | // Created by fancymax on 15/8/4. 6 | // Copyright (c) 2015年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | import PromiseKit 12 | 13 | class Service { 14 | 15 | static let sharedInstance = Service() 16 | 17 | private init(){ } 18 | 19 | static var Manager : Alamofire.SessionManager = { 20 | // Create the server trust policies 21 | let serverTrustPolicies: [String: ServerTrustPolicy] = ["kyfw.12306.cn": ServerTrustPolicy.performDefaultEvaluation(validateHost: true)] 22 | 23 | // Create custom manager 24 | let headers = [ 25 | "refer": "https://kyfw.12306.cn/otn/leftTicket/init", 26 | "Host": "kyfw.12306.cn", 27 | "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Safari/604.1.38", 28 | "Connection" : "keep-alive"] 29 | let configuration = URLSessionConfiguration.default 30 | 31 | configuration.httpCookieAcceptPolicy = .always 32 | configuration.httpAdditionalHeaders = headers 33 | configuration.timeoutIntervalForRequest = 10 34 | 35 | let manager = Alamofire.SessionManager( 36 | configuration: configuration, 37 | serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies) 38 | ) 39 | return manager 40 | }() 41 | 42 | @objc var passport_appId = "otn" 43 | @objc var passport_login = "https://kyfw.12306.cn/passport/web/login" 44 | @objc var passport_captcha = "https://kyfw.12306.cn/passport/captcha/captcha-image" 45 | @objc var passport_authuam = "https://kyfw.12306.cn/passport/web/auth/uamtk" 46 | @objc var passport_captcha_check = "https://kyfw.12306.cn/passport/captcha/captcha-check" 47 | @objc var passport_authclient = "uamauthclient" 48 | @objc var passport_loginPage = "login/init" 49 | @objc var passport_okPage = "index/initMy12306" 50 | 51 | func getConfigFromInitContent(_ content:String) { 52 | func getMatchByKey(_ key:String, matchVar:inout String) { 53 | if let matches = Regex("var \(key) = '([^']+)'").getMatches(content){ 54 | matchVar = matches[0][0] 55 | } 56 | else{ 57 | logger.error("fail to get \(key)") 58 | } 59 | } 60 | 61 | getMatchByKey(#keyPath(passport_appId),matchVar: &passport_appId) 62 | getMatchByKey(#keyPath(passport_login),matchVar: &passport_login) 63 | getMatchByKey(#keyPath(passport_captcha),matchVar: &passport_captcha) 64 | getMatchByKey(#keyPath(passport_authuam),matchVar: &passport_authuam) 65 | getMatchByKey(#keyPath(passport_captcha_check),matchVar: &passport_captcha_check) 66 | getMatchByKey(#keyPath(passport_authclient),matchVar: &passport_authclient) 67 | } 68 | 69 | func jc_getcookie(key:String) -> String? { 70 | return nil 71 | } 72 | 73 | let referKey = "refer" 74 | let referValueForLoginInit = "https://kyfw.12306.cn/otn/login/init" 75 | 76 | func requestDynamicJs(_ jsName:String,referHeader:[String:String])->Promise{ 77 | return Promise{ fulfill, reject in 78 | let url = "https://kyfw.12306.cn/otn/dynamicJs/" + jsName 79 | Service.Manager.request(url, headers:referHeader).response(completionHandler:{ _ in 80 | fulfill() 81 | }) 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /12306ForMac/Service/ServiceError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceError.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/3/9. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func translate(_ error:NSError)->String{ 12 | logger.error(error) 13 | 14 | if error.domain == "NSURLErrorDomain"{ 15 | if error.code == -1009 { 16 | return "网络连接失败,请检查连接或稍后再试" 17 | } 18 | } 19 | if let err = error.localizedFailureReason{ 20 | return err 21 | } 22 | else{ 23 | return error.localizedDescription 24 | } 25 | } 26 | 27 | struct ServiceError { 28 | static let Domain = "com.12306Service.error" 29 | 30 | enum Code: Int { 31 | case loginFailed = -7000 32 | case queryTicketFailed = -7001 33 | case getRandCodeFailed = -7003 34 | case checkRandCodeFailed = -7004 35 | case checkUserFailed = -7005 36 | case submitOrderFailed = -7006 37 | case checkOrderInfoFailed = -7007 38 | case confirmSingleForQueueFailed = -7008 39 | case cancelOrderFailed = -7009 40 | case zeroOrderFailed = -7010 41 | case queryTicketNoFailed = -7011 42 | case queryTicketPriceFailed = -7012 43 | case getPassengerFailed = -7013 44 | case loginUserFailed = -7014 45 | case autoSumbitOrderFailed = -7015 46 | case queryOrderWaitTimeFailed = -7016 47 | case getQueueCountFailed = -7017 48 | } 49 | 50 | static let errorDic = [ 51 | Code.loginFailed:"登录失败", 52 | Code.queryTicketFailed:"未能查到任何车次,请检查查询设置", 53 | Code.autoSumbitOrderFailed: "自动提交订单失败", 54 | Code.getRandCodeFailed: "获取验证码失败", 55 | Code.checkRandCodeFailed: "验证码错误", 56 | Code.checkUserFailed: "非登录状态,需要重新登录", 57 | Code.submitOrderFailed: "提交订单失败", 58 | Code.checkOrderInfoFailed: "订单信息错误", 59 | Code.confirmSingleForQueueFailed: "锁定订单失败", 60 | Code.getQueueCountFailed: "获取排队信息失败", 61 | Code.queryOrderWaitTimeFailed: "查询订单剩余时间失败", 62 | Code.cancelOrderFailed: "取消订单失败", 63 | Code.zeroOrderFailed:"您没有历史订单", 64 | Code.queryTicketNoFailed:"查询车次详细信息失败", 65 | Code.getPassengerFailed:"查询乘客信息失败", 66 | Code.queryTicketPriceFailed:"查询票价失败", 67 | Code.loginUserFailed:"登录用户失败"] 68 | 69 | static func errorWithCode(_ code:Code)->NSError{ 70 | if errorDic.keys.contains(code) { 71 | return errorWithCode(code, failureReason: errorDic[code]!) 72 | } 73 | else { 74 | return errorWithCode(code, failureReason: "未知错误, 错误码 = \(code.rawValue)") 75 | } 76 | } 77 | 78 | static func errorWithCode(_ code: Code, failureReason: String) -> NSError { 79 | let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason] 80 | return NSError(domain: Domain, code: code.rawValue, userInfo: userInfo) 81 | } 82 | 83 | static func isCheckRandCodeError(_ error:NSError)->Bool { 84 | if error.code == Code.checkRandCodeFailed.rawValue { 85 | return true 86 | } 87 | else { 88 | return false 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /12306ForMac/Sheets/LoginWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginWindowController.swift 3 | // Train12306 4 | // 5 | // Created by fancymax on 15/8/10. 6 | // Copyright (c) 2015年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class LoginWindowController: BaseWindowController{ 12 | 13 | @IBOutlet weak var passWord: NSSecureTextField! 14 | @IBOutlet weak var userName: AutoCompleteTextField! 15 | @IBOutlet weak var loginImage: RandCodeImageView2! 16 | @IBOutlet weak var nextImageBtn: NSButton! 17 | 18 | var users = [UserX]() 19 | var isAutoLogin = false 20 | 21 | private var isLogin = false 22 | private var spaceKeyboardMonitor:Any! 23 | 24 | override var windowNibName: String{ 25 | return "LoginWindowController" 26 | } 27 | 28 | override func windowDidLoad() { 29 | if let lastName = QueryDefaultManager.sharedInstance.lastUserName, 30 | let lastPassword = QueryDefaultManager.sharedInstance.lastUserPassword{ 31 | userName.stringValue = lastName 32 | passWord.stringValue = lastPassword 33 | } 34 | 35 | userName.tableViewDelegate = self 36 | 37 | users = DataManger.sharedInstance.queryAllUsers() 38 | 39 | loadImage() 40 | 41 | //增加对Space按键的支持 42 | spaceKeyboardMonitor = NSEvent.addLocalMonitorForEvents(matching: NSKeyDownMask) { [weak self] (theEvent) -> NSEvent? in 43 | //Space Key 44 | if (theEvent.keyCode == 49){ 45 | if let weakSelf = self { 46 | weakSelf.clickOK(nil) 47 | } 48 | return nil 49 | } 50 | return theEvent 51 | } 52 | } 53 | 54 | private func handlerAfterSuccess(){ 55 | QueryDefaultManager.sharedInstance.lastUserName = userName.stringValue 56 | QueryDefaultManager.sharedInstance.lastUserPassword = passWord.stringValue 57 | 58 | //save user 59 | let updateUser = users.filter({$0.name == userName.stringValue}) 60 | if updateUser.count > 0 { 61 | for user in updateUser where user.password != passWord.stringValue { 62 | user.password = passWord.stringValue 63 | DataManger.sharedInstance.updateUser(user) 64 | } 65 | } 66 | else { 67 | let user = UserX() 68 | user.name = userName.stringValue 69 | user.password = passWord.stringValue 70 | DataManger.sharedInstance.inserUser(user) 71 | } 72 | 73 | self.dismissWithModalResponse(NSModalResponseOK) 74 | } 75 | 76 | func loadImage(){ 77 | self.loginImage.clearRandCodes() 78 | self.startLoadingTip("正在加载...") 79 | self.nextImageBtn.isEnabled = false 80 | 81 | let autoLoginHandler = {(image:NSImage)->() in 82 | self.startLoadingTip("自动打码...") 83 | 84 | Dama.sharedInstance.dama(AdvancedPreferenceManager.sharedInstance.damaUser, 85 | password: AdvancedPreferenceManager.sharedInstance.damaPassword, 86 | ofImage: image, 87 | success: {imageCode in 88 | self.stopLoadingTip() 89 | self.loginImage.drawDamaCodes(imageCode) 90 | self.clickOK(nil) 91 | }, 92 | failure: {error in 93 | self.stopLoadingTip() 94 | self.showTip(translate(error)) 95 | }) 96 | } 97 | 98 | let successHandler = {(image:NSImage) -> () in 99 | self.loginImage.image = image 100 | self.stopLoadingTip() 101 | self.nextImageBtn.isEnabled = true 102 | 103 | if ((self.isAutoLogin)&&(AdvancedPreferenceManager.sharedInstance.isUseDama)) { 104 | autoLoginHandler(image) 105 | } 106 | } 107 | let failureHandler = {(error:NSError) -> () in 108 | self.stopLoadingTip() 109 | self.nextImageBtn.isEnabled = true 110 | self.showTip(translate(error)) 111 | } 112 | Service.sharedInstance.preLoginFlow(success: successHandler,failure: failureHandler) 113 | } 114 | 115 | override func dismissWithModalResponse(_ response:NSModalResponse) 116 | { 117 | if window != nil { 118 | if window!.sheetParent != nil { 119 | window!.sheetParent!.endSheet(window!,returnCode: response) 120 | NSEvent.removeMonitor(spaceKeyboardMonitor!) 121 | } 122 | } 123 | } 124 | 125 | @IBAction func clickNextImage(_ sender: NSButton) 126 | { 127 | loadImage() 128 | } 129 | 130 | @IBAction func clickOK(_ sender:AnyObject?){ 131 | if userName.stringValue == "" || passWord.stringValue == "" { 132 | self.showTip("请先输入用户名和密码") 133 | return 134 | } 135 | if loginImage.randCodeStr == nil { 136 | self.showTip("请先选择验证码") 137 | return 138 | } 139 | 140 | if isLogin { 141 | return 142 | } 143 | 144 | isLogin = true 145 | self.startLoadingTip("正在登录...") 146 | 147 | let failureHandler = {(error:NSError) -> () in 148 | self.isLogin = false 149 | self.stopLoadingTip() 150 | self.showTip(translate(error)) 151 | 152 | if ServiceError.isCheckRandCodeError(error) { 153 | DispatchQueue.main.asyncAfter(deadline: .now() + Double(2)) { 154 | self.loadImage() 155 | } 156 | } 157 | else { 158 | //刷新太快,请稍后重试 159 | } 160 | } 161 | 162 | let successHandler = { 163 | self.stopLoadingTip() 164 | self.handlerAfterSuccess() 165 | } 166 | 167 | Service.sharedInstance.loginFlow(user: userName.stringValue, passWord: passWord.stringValue, randCodeStr: loginImage.randCodeStr!, success: successHandler, failure: failureHandler) 168 | } 169 | 170 | @IBAction func clickCancel(_ button:NSButton){ 171 | dismissWithModalResponse(NSModalResponseCancel) 172 | } 173 | } 174 | 175 | // MARK: - AutoCompleteTableViewDelegate 176 | extension LoginWindowController: AutoCompleteTableViewDelegate{ 177 | func textField(_ textField: NSTextField, completions words: [String], forPartialWordRange charRange: NSRange, indexOfSelectedItem index: Int) -> [String] { 178 | var matches = [String]() 179 | for user in self.users { 180 | if let _ = user.name.range(of: textField.stringValue, options: NSString.CompareOptions.anchored) 181 | { 182 | matches.append(user.name) 183 | } 184 | } 185 | return matches 186 | } 187 | 188 | func textField(_ textField:NSTextField,didSelectItem item: String){ 189 | for user in self.users where user.name == item { 190 | self.passWord.stringValue = user.password 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /12306ForMac/StationData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationDataService.swift 3 | // Train12306 4 | // 5 | // Created by fancymax on 15/8/4. 6 | // Copyright (c) 2015年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Station { 12 | //首字母拼音 比如 bj 13 | var FirstLetter:String 14 | //车站名 15 | var Name:String 16 | //电报码 17 | var Code:String 18 | //全拼 19 | var Spell:String 20 | } 21 | 22 | // "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js" 23 | class StationNameJs{ 24 | fileprivate static let sharedManager = StationNameJs() 25 | class var sharedInstance: StationNameJs { 26 | return sharedManager 27 | } 28 | 29 | var allStation:[Station] 30 | 31 | var allStationMap:[String:Station] 32 | 33 | fileprivate init() 34 | { 35 | self.allStation = [Station]() 36 | self.allStationMap = [String:Station]() 37 | 38 | //1.8983 39 | let path = Bundle.main.path(forResource: "station_name", ofType: "js") 40 | let stationInfo = try! NSString(contentsOfFile: path!, encoding: String.Encoding.utf8.rawValue) as String 41 | 42 | if let matches = Regex("@[a-z]+\\|([^\\|]+)\\|([a-z]+)\\|([a-z]+)\\|([a-z]+)\\|").getMatches(stationInfo) 43 | { 44 | for match in matches 45 | { 46 | let oneStation = Station(FirstLetter:match[3],Name:match[0],Code:match[1],Spell:match[2]) 47 | self.allStation.append(oneStation) 48 | self.allStationMap[oneStation.Name] = oneStation 49 | } 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /12306ForMac/TicketViewControllers/PassengerSelectViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PassengerSelectViewController.swift 3 | // 4 | // 5 | // Created by fancymax on 15/10/7. 6 | // 7 | // 8 | 9 | import Cocoa 10 | 11 | class PassengerSelectViewController: NSViewController,NSTableViewDataSource,NSTableViewDelegate{ 12 | @IBOutlet weak var passengerTable: NSTableView! 13 | var passengers = [PassengerDTO]() 14 | let maxSelectedPassengerCount = 5 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | } 19 | 20 | func reloadPassenger(_ passengersToShow:[PassengerDTO]){ 21 | self.passengers = passengersToShow 22 | self.passengerTable.reloadData() 23 | } 24 | 25 | func numberOfRows(in tableView: NSTableView) -> Int { 26 | return self.passengers.count 27 | } 28 | 29 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { 30 | return self.passengers[row] 31 | } 32 | 33 | @IBAction func checkPassenger(_ sender:NSButton){ 34 | if isMaxPassengerNumber(exclude:sender.title) { 35 | sender.state = NSOffState 36 | 37 | for passenger in passengers{ 38 | if passenger.passenger_name == sender.title { 39 | passenger.isChecked = false 40 | break 41 | } 42 | } 43 | 44 | return 45 | } 46 | 47 | let row = passengerTable.row(for: sender) 48 | NotificationCenter.default.post(name: Notification.Name.App.DidCheckPassenger, object: passengers[row].passenger_id_no) 49 | } 50 | 51 | func isMaxPassengerNumber(exclude excludePassenger:String)->Bool { 52 | var count = 0 53 | for passenger in passengers{ 54 | if (passenger.isChecked)&&(passenger.passenger_name != excludePassenger) { 55 | count += 1 56 | } 57 | } 58 | 59 | if count >= maxSelectedPassengerCount { 60 | return true 61 | } 62 | else { 63 | return false 64 | } 65 | } 66 | 67 | func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { 68 | return AutoCompleteTableRowView() 69 | } 70 | 71 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { 72 | return false 73 | } 74 | 75 | deinit{ 76 | NotificationCenter.default.removeObserver(self) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /12306ForMac/TicketViewControllers/PassengerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalListObjectViewController.swift 3 | // 4 | // 5 | // Created by fancymax on 15/10/5. 6 | // 7 | // 8 | 9 | import Cocoa 10 | 11 | class PassengerViewController: NSViewController { 12 | 13 | var passenger:PassengerDTO? 14 | 15 | @IBOutlet weak var passengerCheckBox: NSButton! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | passengerCheckBox.title = passenger!.passenger_name 21 | } 22 | 23 | @IBAction func unSelectPassenger(_ sender: NSButton) { 24 | passenger?.isChecked = false 25 | sender.state = NSOnState 26 | self.view.isHidden = true 27 | } 28 | 29 | func select(){ 30 | if passenger!.isChecked { 31 | self.view.isHidden = false 32 | } 33 | else{ 34 | self.view.isHidden = true 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /12306ForMac/TicketViewControllers/PassengerViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /12306ForMac/TicketViewControllers/TrainCodeDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrainCodeDetailViewController.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/06/12. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TrainCodeDetailViewController: NSViewController { 12 | @IBOutlet weak var priceLbl: NSTextField! 13 | @IBOutlet weak var trainCodeDetailTable: NSTableView! 14 | 15 | private let _trainDetailParam: QueryTrainCodeParam 16 | private let _trainPriceParam: QueryTrainPriceParam 17 | var trainCodeDetails: TrainCodeDetails? 18 | 19 | init(trainDetailParams:QueryTrainCodeParam,trainPriceParams:QueryTrainPriceParam) { 20 | _trainDetailParam = trainDetailParams 21 | _trainPriceParam = trainPriceParams 22 | super.init(nibName: nil, bundle: nil)! 23 | } 24 | 25 | required init?(coder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | // setup Table Header 33 | for col in trainCodeDetailTable.tableColumns { 34 | col.headerCell = TrainCodeDetailHeaderCell(textCell: col.headerCell.stringValue) 35 | col.headerCell.alignment = .center 36 | } 37 | 38 | let successHandler = { (trainDetails:TrainCodeDetails)->() in 39 | self.trainCodeDetails = trainDetails 40 | self.trainCodeDetailTable.reloadData() 41 | } 42 | let failureHandler = {(error:NSError)->() in } 43 | Service.sharedInstance.queryTrainDetailFlowWith(_trainDetailParam, success: successHandler, failure: failureHandler) 44 | 45 | let priceSuccessHandler = { (trainPrice:TrainPrice)->() in 46 | self.priceLbl.stringValue = trainPrice.trainPriceStr 47 | 48 | } 49 | Service.sharedInstance.queryTrainPriceFlowWith(_trainPriceParam, success: priceSuccessHandler, failure: failureHandler) 50 | } 51 | 52 | } 53 | 54 | // MARK: - NSTableViewDataSource 55 | extension TrainCodeDetailViewController: NSTableViewDataSource{ 56 | func numberOfRows(in tableView: NSTableView) -> Int { 57 | if trainCodeDetails == nil { 58 | return 0 59 | } 60 | return trainCodeDetails!.trainNos.count 61 | } 62 | 63 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { 64 | return trainCodeDetails!.trainNos[row] 65 | } 66 | } 67 | 68 | // MARK: - NSTableViewDelegate 69 | extension TrainCodeDetailViewController:NSTableViewDelegate { 70 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { 71 | return false 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/ClickableDatePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClickableDatePicker.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/8/2. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ClickableDatePicker: NSDatePicker { 12 | 13 | @IBInspectable var clickable:Bool = true 14 | 15 | override func mouseDown(with theEvent: NSEvent) { 16 | if self.clickable { 17 | sendAction(self.action, to: self.target) 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/ContentBackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentBackgroundView.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/6/15. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ContentBackgroundView: NSView { 12 | 13 | override func draw(_ dirtyRect: NSRect) { 14 | super.draw(dirtyRect) 15 | 16 | Theme.GobalTheme.backgroundColor.set() 17 | NSRectFill(dirtyRect) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/FilterTrainCodeTransformer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterTrainCodeTransformer.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/9/12. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @objc(FilterTrainCodeTransformer) 12 | class FilterTrainCodeTransformer: ValueTransformer { 13 | 14 | override class func allowsReverseTransformation()->Bool { 15 | return false 16 | } 17 | 18 | override class func transformedValueClass() -> AnyClass { 19 | return NSString.self 20 | } 21 | 22 | override func transformedValue(_ value: Any?) -> Any? { 23 | if value == nil { 24 | return nil 25 | } 26 | let presentation = value as! String 27 | let range = presentation.range(of: "|1")! 28 | return presentation.substring(to: range.lowerBound) 29 | } 30 | 31 | } 32 | 33 | @objc(FilterTrainTimeTransformer) 34 | class FilterTrainTimeTransformer: ValueTransformer { 35 | 36 | override class func allowsReverseTransformation()->Bool { 37 | return false 38 | } 39 | 40 | override class func transformedValueClass() -> AnyClass { 41 | return NSString.self 42 | } 43 | 44 | override func transformedValue(_ value: Any?) -> Any? { 45 | if value == nil { 46 | return nil 47 | } 48 | let presentation = value as! String 49 | let range1 = presentation.range(of: "|1")! 50 | let range2 = presentation.range(of: "|2")! 51 | return presentation.substring(with: range1.upperBound..Bool { 59 | return false 60 | } 61 | 62 | override class func transformedValueClass() -> AnyClass { 63 | return NSString.self 64 | } 65 | 66 | override func transformedValue(_ value: Any?) -> Any? { 67 | if value == nil { 68 | return nil 69 | } 70 | let presentation = value as! String 71 | let range = presentation.range(of: "|2")! 72 | return presentation.substring(from: range.upperBound) 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/GlassView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GlassView.swift 3 | // Train12306 4 | // 5 | // Created by fancymax on 16/1/28. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class GlassView: NSView{ 12 | var backgroundColor:NSColor? 13 | 14 | required init?(coder: NSCoder) { 15 | super.init(coder: coder) 16 | self.commonInit() 17 | } 18 | 19 | override init(frame frameRect: NSRect) { 20 | super.init(frame: frameRect) 21 | self.commonInit() 22 | } 23 | 24 | func commonInit(){ 25 | self.backgroundColor = NSColor.clear 26 | } 27 | 28 | override func draw(_ dirtyRect: NSRect) { 29 | NSGraphicsContext.saveGraphicsState() 30 | self.backgroundColor?.set() 31 | NSRectFill(dirtyRect) 32 | NSGraphicsContext.restoreGraphicsState() 33 | } 34 | 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/InfoButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoButton.swift 3 | // InfoButton 4 | // 5 | // Created by Kauntey Suryawanshi on 25/06/15. 6 | // Copyright (c) 2015 Kauntey Suryawanshi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | @IBDesignable 13 | open class InfoButton : NSControl, NSPopoverDelegate { 14 | var mainSize: CGFloat! 15 | 16 | @IBInspectable var showOnHover: Bool = false 17 | @IBInspectable var fillMode: Bool = true 18 | @IBInspectable var animatePopover: Bool = false 19 | @IBInspectable var content: String = "" 20 | @IBInspectable var primaryColor: NSColor = NSColor.scrollBarColor 21 | var secondaryColor: NSColor = NSColor.white 22 | 23 | var mouseInside = false { 24 | didSet { 25 | self.needsDisplay = true 26 | if showOnHover { 27 | if popover == nil { 28 | popover = NSPopover(content: self.content, doesAnimate: self.animatePopover) 29 | } 30 | if mouseInside { 31 | popover.show(relativeTo: self.frame, of: self.superview!, preferredEdge: NSRectEdge.maxX) 32 | } else { 33 | popover.close() 34 | } 35 | 36 | } 37 | } 38 | } 39 | 40 | var trackingArea: NSTrackingArea! 41 | override open func updateTrackingAreas() { 42 | super.updateTrackingAreas() 43 | if trackingArea != nil { 44 | self.removeTrackingArea(trackingArea) 45 | } 46 | trackingArea = NSTrackingArea(rect: self.bounds, options: [NSTrackingAreaOptions.mouseEnteredAndExited, NSTrackingAreaOptions.activeAlways], owner: self, userInfo: nil) 47 | self.addTrackingArea(trackingArea) 48 | } 49 | 50 | fileprivate var stringAttributeDict = [String: AnyObject]() 51 | fileprivate var circlePath: NSBezierPath! 52 | 53 | var popover: NSPopover! 54 | 55 | 56 | required public init?(coder: NSCoder) { 57 | super.init(coder: coder) 58 | let frameSize = self.frame.size 59 | if frameSize.width != frameSize.height { 60 | self.frame.size.height = self.frame.size.width 61 | } 62 | self.mainSize = self.frame.size.height 63 | stringAttributeDict[NSFontAttributeName] = NSFont.systemFont(ofSize: mainSize * 0.6) 64 | 65 | let inSet: CGFloat = 2 66 | let rect = NSMakeRect(inSet, inSet, mainSize - inSet * 2, mainSize - inSet * 2) 67 | circlePath = NSBezierPath(ovalIn: rect) 68 | } 69 | 70 | 71 | override open func draw(_ dirtyRect: NSRect) { 72 | var activeColor: NSColor! 73 | if mouseInside || (popover != nil && popover!.isShown){ 74 | activeColor = primaryColor 75 | } else { 76 | activeColor = primaryColor.withAlphaComponent(0.35) 77 | } 78 | 79 | if fillMode { 80 | activeColor.setFill() 81 | circlePath.fill() 82 | stringAttributeDict[NSForegroundColorAttributeName] = secondaryColor 83 | } else { 84 | activeColor.setStroke() 85 | circlePath.stroke() 86 | stringAttributeDict[NSForegroundColorAttributeName] = (mouseInside ? primaryColor : primaryColor.withAlphaComponent(0.35)) 87 | } 88 | 89 | let attributedString = NSAttributedString(string: "?", attributes: stringAttributeDict) 90 | let stringLocation = NSMakePoint(mainSize / 2 - attributedString.size().width / 2, mainSize / 2 - attributedString.size().height / 2) 91 | attributedString.draw(at: stringLocation) 92 | } 93 | 94 | override open func mouseDown(with theEvent: NSEvent) { 95 | if popover == nil { 96 | popover = NSPopover(content: self.content, doesAnimate: self.animatePopover) 97 | } 98 | if popover.isShown { 99 | popover.close() 100 | } else { 101 | popover.show(relativeTo: self.frame, of: self.superview!, preferredEdge: NSRectEdge.maxX) 102 | } 103 | } 104 | 105 | override open func mouseEntered(with theEvent: NSEvent) { mouseInside = true } 106 | override open func mouseExited(with theEvent: NSEvent) { mouseInside = false } 107 | 108 | } 109 | 110 | //MARK: Extension for making a popover from string 111 | extension NSPopover { 112 | 113 | convenience init(content: String, doesAnimate: Bool) { 114 | self.init() 115 | 116 | self.behavior = NSPopoverBehavior.transient 117 | self.animates = doesAnimate 118 | self.contentViewController = NSViewController() 119 | self.contentViewController!.view = NSView(frame: NSZeroRect)//remove this ?? 120 | 121 | let popoverMargin = CGFloat(20) 122 | let textField: NSTextField = { 123 | content in 124 | let textField = NSTextField(frame: NSZeroRect) 125 | 126 | textField.isEditable = false 127 | textField.stringValue = content 128 | textField.isBordered = false 129 | textField.drawsBackground = false 130 | textField.sizeToFit() 131 | textField.setFrameOrigin(NSMakePoint(popoverMargin, popoverMargin)) 132 | return textField 133 | }(content) 134 | 135 | self.contentViewController!.view.addSubview(textField) 136 | var viewSize = textField.frame.size; viewSize.width += (popoverMargin * 2); viewSize.height += (popoverMargin * 2) 137 | self.contentSize = viewSize 138 | 139 | } 140 | } 141 | //NSMinXEdge NSMinYEdge NSMaxXEdge NSMaxYEdge 142 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/LoginButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginButton.swift 3 | // HoverButtonDemo 4 | // 5 | // Created by fancymax on 16/1/29. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class LoginButton: NSButton{ 12 | var hovered: Bool = false 13 | 14 | var textColor: NSColor = NSColor.black 15 | 16 | required init?(coder: NSCoder) { 17 | super.init(coder: coder) 18 | self.commonInit() 19 | } 20 | 21 | override init(frame frameRect: NSRect) { 22 | super.init(frame: frameRect) 23 | self.commonInit() 24 | } 25 | 26 | func commonInit(){ 27 | // self.wantsLayer = true 28 | self.createTrackingArea() 29 | self.hovered = false 30 | } 31 | 32 | fileprivate var trackingArea: NSTrackingArea! 33 | func createTrackingArea(){ 34 | if(self.trackingArea != nil){ 35 | self.removeTrackingArea(self.trackingArea!) 36 | } 37 | let circleRect = self.bounds 38 | let flag = NSTrackingAreaOptions.mouseEnteredAndExited.rawValue + NSTrackingAreaOptions.activeInActiveApp.rawValue 39 | self.trackingArea = NSTrackingArea(rect: circleRect, options: NSTrackingAreaOptions(rawValue: flag), owner: self, userInfo: nil) 40 | self.addTrackingArea(self.trackingArea) 41 | } 42 | 43 | override func mouseEntered(with theEvent: NSEvent) { 44 | if !self.isEnabled { 45 | return 46 | } 47 | 48 | self.hovered = true 49 | self.needsDisplay = true 50 | } 51 | 52 | override func mouseExited(with theEvent: NSEvent) { 53 | self.hovered = false 54 | self.needsDisplay = true 55 | } 56 | 57 | func drawText(_ text:String, inRect:NSRect){ 58 | let aParagraghStyle = NSMutableParagraphStyle() 59 | aParagraghStyle.lineBreakMode = NSLineBreakMode.byWordWrapping 60 | aParagraghStyle.alignment = NSTextAlignment.left 61 | 62 | let attrs = [NSParagraphStyleAttributeName:aParagraghStyle, NSFontAttributeName:self.font!, NSForegroundColorAttributeName:self.textColor] as [String : Any] 63 | let size = (text as NSString).size(withAttributes: attrs) 64 | let r:NSRect = NSMakeRect(inRect.origin.x, 65 | inRect.origin.y + (inRect.size.height - size.height)/2.0 - 2, 66 | inRect.size.width, 67 | size.height) 68 | (text as NSString).draw(in: r, withAttributes: attrs) 69 | } 70 | 71 | override func draw(_ dirtyRect: NSRect) { 72 | NSGraphicsContext.saveGraphicsState() 73 | 74 | var imageRect = NSRect() 75 | if let image = self.image{ 76 | imageRect.size.height = self.bounds.height - 6 77 | imageRect.size.width = imageRect.size.height 78 | imageRect.origin.x = 3 79 | imageRect.origin.y = 3 80 | image.draw(in: imageRect) 81 | } 82 | var textRect = self.bounds 83 | textRect.origin.x += imageRect.size.width + 10 84 | textRect.origin.y += 1 85 | textRect.size.width -= imageRect.size.width + 5 86 | 87 | if (hovered){ 88 | var rect = self.bounds 89 | rect.size.height -= 2 90 | rect.size.width -= 2 91 | rect.origin.x += 1 92 | rect.origin.y += 1 93 | let bgPath = NSBezierPath(roundedRect: rect, xRadius: 5, yRadius: 5) 94 | NSColor.lightGray.set() 95 | bgPath.lineWidth = 1 96 | bgPath.stroke() 97 | } 98 | 99 | drawText(self.title, inRect: textRect) 100 | 101 | NSGraphicsContext.restoreGraphicsState() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/LunarCalendar/LunarSolarConverter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LunarSolarConverter.swift 3 | // LunarSolarConverter 4 | // 5 | // Created by isee15 on 15/1/17. 6 | // Copyright (c) 2015年 isee15. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class LunarSolarConverter { 12 | static private let lunarDayStrs = ["初一","初二","初三","初四","初五","初六","初七","初八","初九","初十", 13 | "十一","十二","十三","十四","十五","十六","十七","十八","十九","二十", 14 | "廿一","廿二","廿三","廿四","廿五","廿六","廿七","廿八","廿九","三十","卅一","卅二"] 15 | static private let lunarMonthStrs = ["春节","二月","三月","四月","五月","六月","七月","八月","九月","十月","冬月","腊月"] 16 | 17 | static private let solarFestival = [ 18 | "d0101":"元旦节", 19 | "d0214":"情人节", 20 | "d0308":"妇女节", 21 | "d0312":"植树节", 22 | "d0401":"愚人节", 23 | "d0501":"劳动节", 24 | "d0504":"青年节", 25 | "d0601":"儿童节", 26 | "d0701":"建党节", 27 | "d0801":"建军节", 28 | "d0910":"教师节", 29 | "d1001":"国庆节", 30 | "d1031":"万圣节", 31 | "d1111":"光棍节", 32 | "d1224":"平安夜", 33 | "d1225":"圣诞节" 34 | ] 35 | static private let lunarFestival = [ 36 | "d0101":"春节", 37 | "d0115":"元宵节", 38 | "d0505":"端午节", 39 | "d0707":"七夕节", 40 | "d0815":"中秋节", 41 | "d1223":"小年", 42 | "d0100":"除夕" //除夕暂不支持显示 43 | ] 44 | 45 | static private func formatDay(month:Int,day:Int) -> String { 46 | return String(format: "d%02d%02d", month,day) 47 | } 48 | 49 | static func Conventer2lunarStr(_ solar: Date) -> String{ 50 | let solarComponents = Calendar.current.dateComponents([.day,.month], from: solar) 51 | let solarFestivalKey = formatDay(month: solarComponents.month!, day: solarComponents.day!) 52 | for key in solarFestival.keys { 53 | if key == solarFestivalKey { 54 | return solarFestival[key]! 55 | } 56 | } 57 | 58 | let lunarCal = Calendar(identifier: Calendar.Identifier.chinese) 59 | let lunarComponents = lunarCal.dateComponents([.day,.month], from: solar) 60 | let lunarFestivalKey = formatDay(month: lunarComponents.month!, day: lunarComponents.day!) 61 | for key in lunarFestival.keys { 62 | if key == lunarFestivalKey { 63 | return lunarFestival[key]! 64 | } 65 | } 66 | 67 | if lunarComponents.day != 1 { 68 | return lunarDayStrs[lunarComponents.day! - 1] 69 | } 70 | else{ 71 | return lunarMonthStrs[lunarComponents.month! - 1] 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/NSTableView+ContextMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableView+ContextMenu.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/12/6. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | //user define Context Menu 13 | protocol ContextMenuDelegate: NSObjectProtocol { 14 | func tableView(aTableView:NSTableView, menuForRows rows:IndexSet) -> NSMenu? 15 | } 16 | 17 | extension NSTableView { 18 | 19 | open override func menu(for event: NSEvent) -> NSMenu? { 20 | 21 | let location = self.convert(event.locationInWindow, from: nil) 22 | let row = self.row(at: location) 23 | if ((row < 0) || (event.type != NSRightMouseDown)) { 24 | return super.menu(for: event) 25 | } 26 | 27 | var selected = self.selectedRowIndexes 28 | if !selected.contains(row) { 29 | selected = IndexSet(integer:row) 30 | self.selectRowIndexes(selected, byExtendingSelection: false) 31 | } 32 | 33 | if let contextMenuDelegate = self.delegate { 34 | if contextMenuDelegate.responds(to: Selector(("tableView:menuForRows:"))){ 35 | return (contextMenuDelegate as! ContextMenuDelegate).tableView(aTableView: self,menuForRows:selected) 36 | } 37 | } 38 | 39 | return super.menu(for: event) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/RandCodeImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandCodeImageView.swift 3 | // Train12306 4 | // 5 | // Created by fancymax on 15/8/12. 6 | // Copyright (c) 2015年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class RandCodeImageView:NSImageView { 12 | 13 | struct ImageDot { 14 | var randCodeX: Int 15 | var randCodeY: Int 16 | var pointX:CGFloat 17 | var pointY:CGFloat 18 | } 19 | 20 | fileprivate var imageDots = [ImageDot]() 21 | 22 | var randCodeStr:String?{ 23 | get{ 24 | if imageDots.count == 0{ 25 | return nil 26 | } 27 | var str = "\(imageDots[0].randCodeX),\(imageDots[0].randCodeY)" 28 | if imageDots.count >= 2 29 | { 30 | for i in 1...imageDots.count - 1{ 31 | str = str + ",\(imageDots[i].randCodeX),\(imageDots[i].randCodeY)" 32 | } 33 | } 34 | return str 35 | } 36 | } 37 | 38 | func clearRandCodes() 39 | { 40 | imageDots = [ImageDot]() 41 | needsDisplay = true 42 | } 43 | 44 | override func mouseDown(with theEvent: NSEvent) { 45 | let frameOffsetInWindow = convert(self.frame.origin, from: nil) 46 | 47 | let imageOriginX = self.frame.origin.x - frameOffsetInWindow.x 48 | let imageOriginY = self.frame.origin.y + self.bounds.height - frameOffsetInWindow.y 49 | let mouseX = theEvent.locationInWindow.x 50 | let mouseY = theEvent.locationInWindow.y 51 | let randCodeX = (mouseX - imageOriginX)/1.2 52 | let randCodeY = (imageOriginY - mouseY)/1.2 - 30 53 | 54 | if ((randCodeX < 0) || (randCodeY < 0)){ 55 | return 56 | } 57 | 58 | let pointX = mouseX - (self.frame.origin.x - frameOffsetInWindow.x) 59 | let pointY = mouseY - (self.frame.origin.y - frameOffsetInWindow.y) 60 | 61 | var isAdd = true 62 | if imageDots.count != 0 63 | { 64 | for i in 0...imageDots.count - 1 65 | { 66 | if (abs(pointX - imageDots[i].pointX) < 10) && (abs(pointY - imageDots[i].pointY) < 10) 67 | { 68 | imageDots.remove(at: i) 69 | isAdd = false 70 | break 71 | } 72 | } 73 | } 74 | if isAdd 75 | { 76 | imageDots.append(ImageDot(randCodeX: Int(randCodeX), randCodeY: Int(randCodeY), pointX: pointX, pointY: pointY)) 77 | } 78 | 79 | needsDisplay = true 80 | } 81 | 82 | override func draw(_ dirtyRect: NSRect) { 83 | super.draw(dirtyRect) 84 | 85 | NSColor.green.set() 86 | 87 | func drawDot(_ pointX: CGFloat,pointY: CGFloat) 88 | { 89 | let dotRect = CGRect(origin: NSPoint(x: pointX, y: pointY), size: CGSize.zero).insetBy(dx:-10, dy:-10) 90 | NSBezierPath(ovalIn: dotRect).fill() 91 | } 92 | 93 | for point in imageDots 94 | { 95 | drawDot(point.pointX,pointY: point.pointY) 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/RandCodeImageView2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandCodeImageView2.swift 3 | // SelectImageDemo 4 | // 5 | // Created by fancymax on 16/3/8. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class RandCodeImageView2:NSImageView { 12 | struct ImageSection { 13 | var rowIndex: Int 14 | var colIndex: Int 15 | 16 | func isSameSection(_ section:ImageSection)->Bool{ 17 | if (section.rowIndex == rowIndex) && (section.colIndex == colIndex){ 18 | return true 19 | } 20 | else{ 21 | return false 22 | } 23 | } 24 | } 25 | 26 | fileprivate var imageSections = [ImageSection]() 27 | fileprivate func convertSectionToRandCode(_ section:ImageSection)->(Int,Int){ 28 | var randX = 0 29 | var randY = 0 30 | if section.rowIndex == 0 { 31 | randY = 110 32 | } 33 | else{ 34 | randY = 40 35 | } 36 | 37 | if section.colIndex == 0{ 38 | randX = 40 39 | } 40 | else if section.colIndex == 1{ 41 | randX = 110 42 | } 43 | else if section.colIndex == 2{ 44 | randX = 180 45 | } 46 | else{ 47 | randX = 255 48 | } 49 | return (randX,randY) 50 | } 51 | var randCodeStr:String?{ 52 | get{ 53 | if imageSections.count == 0{ 54 | return nil 55 | } 56 | var (randX,randY) = convertSectionToRandCode(imageSections[0]) 57 | var str = "\(randX),\(randY)" 58 | if imageSections.count >= 2{ 59 | for i in 1...imageSections.count - 1 { 60 | (randX, randY) = convertSectionToRandCode(imageSections[i]) 61 | str += ",\(randX),\(randY)" 62 | } 63 | } 64 | return str 65 | } 66 | } 67 | 68 | override var acceptsFirstResponder: Bool { 69 | return true 70 | } 71 | 72 | override func becomeFirstResponder() -> Bool { 73 | return true 74 | } 75 | 76 | override func resignFirstResponder() -> Bool { 77 | return true 78 | } 79 | 80 | override func mouseDown(with theEvent: NSEvent) { 81 | let section = indentifySection(theEvent) 82 | 83 | var shouldAdd = true 84 | if imageSections.count > 0{ 85 | for i in 0...imageSections.count - 1 { 86 | if imageSections[i].isSameSection(section){ 87 | imageSections.remove(at: i) 88 | shouldAdd = false 89 | break 90 | } 91 | } 92 | } 93 | 94 | if shouldAdd { 95 | imageSections.append(section) 96 | } 97 | 98 | needsDisplay = true 99 | } 100 | 101 | override func draw(_ dirtyRect: NSRect) { 102 | super.draw(dirtyRect) 103 | 104 | NSColor.red.set() 105 | 106 | for section in imageSections 107 | { 108 | drawSection(section) 109 | } 110 | } 111 | 112 | func clearRandCodes() 113 | { 114 | imageSections = [ImageSection]() 115 | needsDisplay = true 116 | } 117 | 118 | //识别点击在哪个区域 119 | fileprivate func indentifySection(_ theEvent: NSEvent) ->ImageSection{ 120 | var section = ImageSection(rowIndex: 1, colIndex: 1) 121 | 122 | let frameOffsetInWindow = convert(self.frame.origin, from: nil) 123 | let mouseX = theEvent.locationInWindow.x 124 | let mouseY = theEvent.locationInWindow.y 125 | let pointX = mouseX - (self.frame.origin.x - frameOffsetInWindow.x) 126 | let pointY = mouseY - (self.frame.origin.y - frameOffsetInWindow.y) 127 | 128 | if pointY < 100 { 129 | section.rowIndex = 0 130 | } 131 | else{ 132 | section.rowIndex = 1 133 | } 134 | 135 | if pointX < 85 { 136 | section.colIndex = 0 137 | } 138 | else if pointX < 175 { 139 | section.colIndex = 1 140 | } 141 | else if pointX < 260 { 142 | section.colIndex = 2 143 | } 144 | else if pointX < 345 { 145 | section.colIndex = 3 146 | } 147 | else{ 148 | section.colIndex = 3 149 | } 150 | 151 | return section 152 | } 153 | 154 | 155 | //dama to section 156 | fileprivate func damaPoint2Section(X pointX:Double,Y pointY:Double) -> ImageSection{ 157 | var section = ImageSection(rowIndex: 1, colIndex: 1) 158 | 159 | if pointY > 110 { 160 | section.rowIndex = 0 //从下往上 161 | } 162 | else{ 163 | section.rowIndex = 1 164 | } 165 | 166 | if pointX < 75 { 167 | section.colIndex = 0 168 | } 169 | else if pointX < 146 { 170 | section.colIndex = 1 171 | } 172 | else if pointX < 220 { 173 | section.colIndex = 2 174 | } 175 | else{ 176 | section.colIndex = 3 177 | } 178 | return section 179 | } 180 | 181 | //119,65|24,76 182 | func drawDamaCodes(_ damaCodes:String){ 183 | let damaFrameStrs = damaCodes.components(separatedBy: "|") 184 | for damaFrameStr in damaFrameStrs { 185 | let damaFramePair = damaFrameStr.components(separatedBy: ",") 186 | let pointX = Double(damaFramePair[0]) 187 | let pointY = Double(damaFramePair[1]) 188 | 189 | imageSections.append(damaPoint2Section(X:pointX!, Y: pointY!)) 190 | } 191 | needsDisplay = true 192 | } 193 | 194 | //绘制特定正方形区域 195 | fileprivate func drawSection(_ section:ImageSection){ 196 | let point = CGPoint(x: 4 + section.colIndex/2 + section.colIndex * 85, 197 | y: 10 + section.rowIndex + section.rowIndex * 85) 198 | let size = CGSize(width: 85, height: 85) 199 | let rect = NSRect(origin: point, size: size) 200 | let path = NSBezierPath(rect: rect) 201 | 202 | let lineDash:[CGFloat] = [4.0,2.0] 203 | path.setLineDash(lineDash, count: 2, phase: 0.0) 204 | path.flatness = 0.8 205 | path.windingRule = NSWindingRule.evenOddWindingRule 206 | path.lineWidth = 2 207 | path.stroke() 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/Theme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Theme.swift 3 | // WWDC 4 | // 5 | // Created by Guilherme Rambo on 18/04/15. 6 | // Copyright (c) 2015 Guilherme Rambo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | private let _SharedThemeInstance = Theme() 12 | 13 | class Theme: NSObject { 14 | 15 | class var GobalTheme: Theme { 16 | return _SharedThemeInstance 17 | } 18 | 19 | let backgroundColor = NSColor(calibratedRed:0.921569, green:0.921569, blue:0.921569, alpha:1.0) 20 | } -------------------------------------------------------------------------------- /12306ForMac/UserControls/TrainCodeDetailHeaderCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrainCodeDetailHeaderCell.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/6/15. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TrainCodeDetailHeaderCell: NSTableHeaderCell { 12 | 13 | override func draw(withFrame cellFrame: NSRect, in controlView: NSView) { 14 | let (borderRect, fillRect) = cellFrame.divided(atDistance: 1.0, from: .maxYEdge) 15 | 16 | //header bottom line 17 | NSColor(calibratedRed: 0.850887, green: 0.851034, blue: 0.850878, alpha: 1.0).set() 18 | NSRectFill(borderRect) 19 | 20 | Theme.GobalTheme.backgroundColor.set() 21 | NSRectFill(fillRect) 22 | self.drawInterior(withFrame: fillRect.insetBy(dx: 0.0, dy: 1.0), in: controlView) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/TrainInfoTableCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrainStationTableCellView.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/6/10. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TrainTableCellView: NSTableCellView { 12 | var ticketInfo: QueryLeftNewDTO! { 13 | didSet { 14 | updateUI() 15 | } 16 | } 17 | 18 | var selected = false 19 | //change color by backgroundStyle not selected status 20 | override var backgroundStyle: NSBackgroundStyle { 21 | didSet { 22 | updateTint() 23 | } 24 | } 25 | 26 | internal func updateUI() { 27 | 28 | } 29 | 30 | internal func updateTint() { 31 | if backgroundStyle == .dark { 32 | setSelectedControlStyle() 33 | } 34 | else{ 35 | setUnSelectedControlStyle() 36 | } 37 | } 38 | 39 | internal func setSelectedControlStyle() { 40 | 41 | } 42 | 43 | internal func setUnSelectedControlStyle() { 44 | 45 | } 46 | 47 | } 48 | 49 | class TrainStationTableCellView: TrainTableCellView { 50 | 51 | @IBOutlet weak fileprivate var stationMarkField: NSTextField! 52 | @IBOutlet weak fileprivate var stationField: NSTextField! 53 | @IBOutlet weak fileprivate var timeField: NSTextField! 54 | 55 | override func setSelectedControlStyle() { 56 | stationMarkField.textColor = NSColor(calibratedWhite: 1.0, alpha: 0.70) 57 | } 58 | 59 | override func setUnSelectedControlStyle() { 60 | stationMarkField.textColor = NSColor(calibratedWhite: 0.0, alpha: 0.45) 61 | } 62 | } 63 | 64 | // MARK: - 发站 65 | class StartStationTableCellView: TrainStationTableCellView { 66 | override func updateUI() { 67 | stationField.stringValue = ticketInfo.FromStationName! 68 | timeField.stringValue = ticketInfo.start_time! 69 | if ticketInfo.isStartStation { 70 | stationMarkField.isHidden = false 71 | } 72 | else{ 73 | stationMarkField.isHidden = true 74 | } 75 | } 76 | } 77 | 78 | // MARK: - 到站 79 | class EndStationTableCellView: TrainStationTableCellView { 80 | override func updateUI() { 81 | stationField.stringValue = ticketInfo.ToStationName! 82 | timeField.stringValue = ticketInfo.arrive_time! 83 | if ticketInfo.isEndStation { 84 | stationMarkField.isHidden = false 85 | } 86 | else { 87 | stationMarkField.isHidden = true 88 | } 89 | } 90 | } 91 | 92 | // MARK: - 车次 93 | class TrainCodeTableCellView: TrainTableCellView { 94 | @IBOutlet weak fileprivate var trainCodeBtn: NSButton! 95 | 96 | func setTarget(_ target:AnyObject?, action:Selector){ 97 | trainCodeBtn.target = target 98 | trainCodeBtn.action = action 99 | } 100 | } 101 | 102 | // MARK: - 余票信息 103 | class TrainInfoTableCellView: TrainTableCellView { 104 | @IBOutlet weak fileprivate var messageField: NSTextField! 105 | @IBOutlet weak fileprivate var SwzBtn: NSButton! 106 | @IBOutlet weak fileprivate var TzBtn: NSButton! 107 | @IBOutlet weak fileprivate var ZyBtn: NSButton! 108 | @IBOutlet weak fileprivate var ZeBtn: NSButton! 109 | @IBOutlet weak fileprivate var GrBtn: NSButton! 110 | @IBOutlet weak fileprivate var RwBtn: NSButton! 111 | @IBOutlet weak fileprivate var YwBtn: NSButton! 112 | @IBOutlet weak fileprivate var RzBtn: NSButton! 113 | @IBOutlet weak fileprivate var YzBtn: NSButton! 114 | @IBOutlet weak fileprivate var WzBtn: NSButton! 115 | 116 | var dictOfBtn:[Int:NSButton]{ 117 | get { 118 | return [1: SwzBtn, 2: TzBtn, 3: ZyBtn, 4: ZeBtn, 5: GrBtn, 6: RwBtn, 7: YwBtn, 8: RzBtn, 9: YzBtn, 10: WzBtn] 119 | } 120 | } 121 | 122 | func setTarget(_ target:AnyObject?, action:Selector){ 123 | for btn in dictOfBtn.values { 124 | btn.target = target 125 | btn.action = action 126 | } 127 | } 128 | 129 | override func setSelectedControlStyle() { 130 | messageField.textColor = NSColor(calibratedWhite: 1.0, alpha: 0.70) 131 | 132 | for btn in dictOfBtn.values { 133 | (btn as! LoginButton).textColor = NSColor(calibratedWhite: 1.0, alpha: 1) 134 | } 135 | } 136 | 137 | override func setUnSelectedControlStyle() { 138 | messageField.textColor = NSColor(calibratedWhite: 0.0, alpha: 0.45) 139 | 140 | for btn in dictOfBtn.values { 141 | (btn as! LoginButton).textColor = NSColor(calibratedRed: 0.270588, green: 0.541176, blue: 0.913725, alpha: 1.0) 142 | } 143 | } 144 | 145 | override func updateUI() { 146 | 147 | func setTicketButton(_ ticket:String,sender:NSButton){ 148 | if ((ticket == "--")||(ticket == "无")||(ticket == "*")||(ticket == "")){ 149 | sender.isHidden = true 150 | return 151 | } 152 | 153 | sender.isHidden = false 154 | sender.isEnabled = true 155 | if (ticket == "有"){ 156 | sender.title = sender.alternateTitle + "(有票)" 157 | } 158 | else{ 159 | sender.title = sender.alternateTitle + "(\(ticket)张)" 160 | } 161 | 162 | } 163 | 164 | setTicketButton(ticketInfo.Swz_Num, sender: SwzBtn) 165 | setTicketButton(ticketInfo.Tz_Num, sender: TzBtn) 166 | setTicketButton(ticketInfo.Zy_Num, sender: ZyBtn) 167 | setTicketButton(ticketInfo.Ze_Num, sender: ZeBtn) 168 | setTicketButton(ticketInfo.Gr_Num, sender: GrBtn) 169 | setTicketButton(ticketInfo.Rw_Num, sender: RwBtn) 170 | setTicketButton(ticketInfo.Yw_Num, sender: YwBtn) 171 | setTicketButton(ticketInfo.Rz_Num, sender: RzBtn) 172 | setTicketButton(ticketInfo.Yz_Num, sender: YzBtn) 173 | setTicketButton(ticketInfo.Wz_Num, sender: WzBtn) 174 | 175 | if ticketInfo.canWebBuy == "Y" { 176 | messageField.isHidden = true 177 | } 178 | else if ticketInfo.canWebBuy == "N" { 179 | if ticketInfo.hasTicket { 180 | messageField.stringValue = " 本车次无法购买,可重新查询" 181 | } 182 | else { 183 | messageField.stringValue = " 本车次暂无可售车票" 184 | } 185 | messageField.isHidden = false 186 | } 187 | else if ticketInfo.canWebBuy == "IS_TIME_NOT_BUY"{ 188 | 189 | for btn in dictOfBtn.values { 190 | btn.isEnabled = false 191 | } 192 | 193 | if ticketInfo.buttonTextInfo == "23:00-06:00系统维护时间" { 194 | if ticketInfo.hasTicket { 195 | messageField.isHidden = true 196 | } 197 | else{ 198 | messageField.stringValue = " 本车次暂无可售车票" 199 | messageField.isHidden = false 200 | } 201 | } 202 | else 203 | { 204 | if let range = ticketInfo.buttonTextInfo.range(of: "
"){ 205 | ticketInfo.buttonTextInfo.removeSubrange(range) 206 | } 207 | messageField.stringValue = " " + ticketInfo.buttonTextInfo 208 | messageField.isHidden = false 209 | } 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/TrainStationTableCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrainStationTableCellView.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/6/10. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TrainStationTableCellView: NSTableCellView { 12 | 13 | @IBOutlet weak private var stationMarkField: NSTextField! 14 | @IBOutlet weak private var stationField: NSTextField! 15 | @IBOutlet weak private var timeField: NSTextField! 16 | var ticketInfo: QueryLeftNewDTO! { 17 | didSet { 18 | updateUI() 19 | } 20 | } 21 | 22 | var selected = false { 23 | didSet{ 24 | updateTint() 25 | } 26 | } 27 | 28 | override var backgroundStyle: NSBackgroundStyle { 29 | didSet{ 30 | stationMarkField.cell?.backgroundStyle = .Light 31 | } 32 | } 33 | 34 | internal func updateUI() { 35 | 36 | } 37 | 38 | private func updateTint() { 39 | if selected { 40 | stationMarkField.textColor = NSColor(calibratedWhite: 1.0, alpha: 0.70) 41 | } 42 | else{ 43 | stationMarkField.textColor = NSColor(calibratedWhite: 0.0, alpha: 0.70) 44 | } 45 | } 46 | 47 | } 48 | 49 | class StartStationTableCellView: TrainStationTableCellView { 50 | override func updateUI() { 51 | stationField.stringValue = ticketInfo.FromStationName! 52 | timeField.stringValue = ticketInfo.start_time! 53 | if ticketInfo.isStartStation { 54 | stationMarkField.hidden = false 55 | } 56 | else{ 57 | stationMarkField.hidden = true 58 | } 59 | } 60 | } 61 | 62 | class EndStationTableCellView: TrainStationTableCellView { 63 | override func updateUI() { 64 | stationField.stringValue = ticketInfo.ToStationName! 65 | timeField.stringValue = ticketInfo.arrive_time! 66 | if ticketInfo.isEndStation { 67 | stationMarkField.hidden = false 68 | } 69 | else { 70 | stationMarkField.hidden = true 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/TrainTableRowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrainTableRowView.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/6/10. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TrainTableRowView: NSTableRowView { 12 | 13 | override var isOpaque: Bool { 14 | return false 15 | } 16 | 17 | override var isSelected: Bool { 18 | didSet { 19 | updateSubviewsInterestedInSelectionState() 20 | } 21 | } 22 | 23 | fileprivate func updateSubviewsInterestedInSelectionState() { 24 | guard subviews.count > 0 else { return } 25 | 26 | for view in subviews { 27 | if view.isKind(of: TrainTableCellView.self) { 28 | let stationCellView = view as! TrainTableCellView 29 | stationCellView.selected = isSelected 30 | } 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/URLButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLButton.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/7/15. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class URLButton: NSButton { 12 | 13 | fileprivate var hovered: Bool = false 14 | 15 | required init?(coder: NSCoder) { 16 | super.init(coder: coder) 17 | self.commonInit() 18 | } 19 | 20 | override init(frame frameRect: NSRect) { 21 | super.init(frame: frameRect) 22 | self.commonInit() 23 | } 24 | 25 | func commonInit(){ 26 | // self.wantsLayer = true 27 | self.createTrackingArea() 28 | self.hovered = false 29 | } 30 | 31 | fileprivate var trackingArea: NSTrackingArea! 32 | func createTrackingArea(){ 33 | if(self.trackingArea != nil){ 34 | self.removeTrackingArea(self.trackingArea!) 35 | } 36 | let circleRect = self.bounds 37 | let flag = NSTrackingAreaOptions.mouseEnteredAndExited.rawValue + NSTrackingAreaOptions.activeInActiveApp.rawValue 38 | self.trackingArea = NSTrackingArea(rect: circleRect, options: NSTrackingAreaOptions(rawValue: flag), owner: self, userInfo: nil) 39 | self.addTrackingArea(self.trackingArea) 40 | } 41 | 42 | override func mouseEntered(with theEvent: NSEvent) { 43 | self.hovered = true 44 | self.needsDisplay = true 45 | } 46 | 47 | override func mouseExited(with theEvent: NSEvent) { 48 | self.hovered = false 49 | self.needsDisplay = true 50 | } 51 | 52 | override func resetCursorRects() { 53 | super.resetCursorRects() 54 | self.addCursorRect(self.bounds, cursor: NSCursor.pointingHand()) 55 | } 56 | 57 | override func draw(_ dirtyRect: NSRect) { 58 | super.draw(dirtyRect) 59 | 60 | if (hovered){ 61 | let bottomLine = NSBezierPath() 62 | bottomLine.move(to: NSMakePoint(NSMinX(bounds), NSMaxY(bounds))) 63 | bottomLine.line(to: NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))) 64 | bottomLine.lineWidth = 2.0 65 | bottomLine.stroke() 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /12306ForMac/UserControls/UrlLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UrlLabel.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/8/10. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class UrlLabel: NSTextField { 12 | 13 | @IBInspectable var urlString:String! 14 | 15 | override init(frame frameRect: NSRect) { 16 | super.init(frame: frameRect) 17 | commonInit() 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | super.init(coder: coder) 22 | commonInit() 23 | } 24 | 25 | func commonInit(){ 26 | self.isSelectable = false 27 | self.isEditable = false 28 | self.drawsBackground = false 29 | 30 | let attrs = [NSForegroundColorAttributeName: NSColor.blue, NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue] as [String : Any] 31 | let attributeStr = NSAttributedString(string: self.stringValue, attributes: attrs) 32 | self.attributedStringValue = attributeStr 33 | } 34 | 35 | override func mouseUp(with theEvent: NSEvent) { 36 | var curPoint = theEvent.locationInWindow 37 | curPoint = self.convert(curPoint, from: nil) 38 | if !NSPointInRect(curPoint, self.bounds) { 39 | return 40 | } 41 | NSWorkspace.shared().open(URL(string: urlString)!); 42 | } 43 | 44 | override func resetCursorRects() { 45 | super.resetCursorRects() 46 | self.addCursorRect(self.bounds, cursor: NSCursor.pointingHand()) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /12306ForMac/Utilities/CalendarManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarManager.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/12/5. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import EventKit 11 | 12 | class CalendarManager:NSObject { 13 | private let eventStore:EKEventStore 14 | private var isAccessToEventStoreGranted:Bool 15 | 16 | fileprivate static let sharedManager = CalendarManager() 17 | class var sharedInstance: CalendarManager { 18 | return sharedManager 19 | } 20 | 21 | fileprivate override init () { 22 | isAccessToEventStoreGranted = false 23 | eventStore = EKEventStore() 24 | } 25 | 26 | private func createCalendar() { 27 | if !isAccessToEventStoreGranted { 28 | return 29 | } 30 | var shouldCreateCalendar = false 31 | if let calendarId = UserDefaults.standard.string(forKey: "calendarId") { 32 | let calendar = eventStore.calendar(withIdentifier: calendarId) 33 | 34 | if calendar == nil { 35 | shouldCreateCalendar = true 36 | } 37 | } 38 | else { 39 | shouldCreateCalendar = true 40 | } 41 | if !shouldCreateCalendar { 42 | return 43 | } 44 | 45 | let calendar = EKCalendar(for: .event, eventStore: eventStore) 46 | calendar.title = "12306ForMac" 47 | calendar.color = NSColor.gray 48 | calendar.source = eventStore.defaultCalendarForNewEvents.source 49 | do { 50 | try self.eventStore.saveCalendar(calendar, commit: true) 51 | logger.info("calendarId = \(calendar.calendarIdentifier)") 52 | UserDefaults.standard.set(calendar.calendarIdentifier, forKey: "calendarId") 53 | } 54 | catch { 55 | logger.error(error) 56 | } 57 | } 58 | 59 | @discardableResult 60 | func updateAuthorizationStatus() -> Bool { 61 | switch EKEventStore.authorizationStatus(for: .event) { 62 | case .authorized: 63 | self.isAccessToEventStoreGranted = true 64 | self.createCalendar() 65 | return true 66 | case .notDetermined: 67 | self.eventStore.requestAccess(to: .event, completion: {[unowned self] granted,error in 68 | DispatchQueue.main.async { 69 | self.isAccessToEventStoreGranted = granted 70 | self.createCalendar() 71 | } 72 | }) 73 | return false 74 | case .restricted, .denied: 75 | isAccessToEventStoreGranted = false 76 | return false 77 | } 78 | } 79 | 80 | func createEvent(title:String, startDate:Date, endDate:Date)->Bool { 81 | if !isAccessToEventStoreGranted { 82 | return false 83 | } 84 | 85 | let createEventHandler = {(calendar:EKCalendar) -> () in 86 | let event = EKEvent(eventStore: self.eventStore) 87 | event.title = title 88 | event.startDate = startDate 89 | event.endDate = endDate 90 | event.calendar = calendar 91 | do { 92 | try self.eventStore.save(event, span: .thisEvent, commit: true) 93 | } catch { 94 | logger.error(error) 95 | } 96 | } 97 | 98 | if let calendarId = UserDefaults.standard.string(forKey: "calendarId") { 99 | if let calendar = eventStore.calendar(withIdentifier: calendarId) { 100 | createEventHandler(calendar) 101 | } 102 | else { 103 | logger.error("Calendar = nil,create") 104 | self.createCalendar() 105 | 106 | if let calendarId = UserDefaults.standard.string(forKey: "calendarId") { 107 | if let calendar = eventStore.calendar(withIdentifier: calendarId) { 108 | createEventHandler(calendar) 109 | } 110 | else{ 111 | logger.error("Calendar = nil,again") 112 | } 113 | } 114 | } 115 | } 116 | return true 117 | } 118 | 119 | } 120 | 121 | -------------------------------------------------------------------------------- /12306ForMac/Utilities/FSPreventSystemSleep.h: -------------------------------------------------------------------------------- 1 | // 2 | // FSPreventSystemSleep.h 3 | // 4 | // Created by fancymax on 16/01/2018. 5 | // 6 | 7 | #import 8 | 9 | @interface FSPreventSystemSleep : NSObject 10 | 11 | -(void) preventSystemSleep:(BOOL)disabled; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /12306ForMac/Utilities/FSPreventSystemSleep.m: -------------------------------------------------------------------------------- 1 | // 2 | // FSPreventSystemSleep.m 3 | // 4 | // Created by fancymax on 16/01/2018. 5 | // 6 | 7 | #import "FSPreventSystemSleep.h" 8 | #import 9 | 10 | NSString * const kMPXPowerSaveAssertion = @"Query Tickets"; 11 | 12 | @implementation FSPreventSystemSleep 13 | IOPMAssertionID nonSleepHandler; 14 | 15 | - (instancetype)init { 16 | if (self = [super init]) { 17 | nonSleepHandler = kIOPMNullAssertionID; 18 | } 19 | 20 | return self; 21 | } 22 | 23 | - (void)dealloc { 24 | if (nonSleepHandler != kIOPMNullAssertionID) { 25 | IOPMAssertionRelease(nonSleepHandler); 26 | nonSleepHandler = kIOPMNullAssertionID; 27 | } 28 | } 29 | 30 | -(void)preventSystemSleep:(BOOL)disabled { 31 | if (disabled) { 32 | if (nonSleepHandler == kIOPMNullAssertionID) { 33 | IOReturn err = 34 | IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleSystemSleep, kIOPMAssertionLevelOn, 35 | (__bridge CFStringRef)kMPXPowerSaveAssertion, &nonSleepHandler); 36 | if (err != kIOReturnSuccess) { 37 | NSLog(@"Can't preventSystemSleep"); 38 | } 39 | } 40 | } else { 41 | if (nonSleepHandler != kIOPMNullAssertionID) { 42 | IOPMAssertionRelease(nonSleepHandler); 43 | nonSleepHandler = kIOPMNullAssertionID; 44 | } 45 | } 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /12306ForMac/Utilities/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2016/11/14. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Notification.Name { 12 | public struct App { 13 | public static let DidLogin = Notification.Name(rawValue: "com.12306ForMac.App.DidLogin") 14 | 15 | public static let DidLogout = Notification.Name(rawValue: "com.12306ForMac.App.DidLogout") 16 | 17 | public static let DidStartQueryTicket = Notification.Name(rawValue: "com.12306ForMac.App.DidStartQueryTicket") 18 | 19 | public static let DidRefilterQueryTicket = Notification.Name(rawValue: "com.12306ForMac.App.DidRefilterQueryTicket") 20 | 21 | public static let DidTrainFilterKeyChange = Notification.Name(rawValue: "com.12306ForMac.App.DidTrainFilterKeyChange") 22 | 23 | public static let DidExcludeTrainSubmit = Notification.Name(rawValue: "com.12306ForMac.App.DidExcludeTrainSubmit") 24 | 25 | public static let DidSubmit = Notification.Name(rawValue: "com.12306ForMac.App.DidSubmit") 26 | 27 | public static let DidCheckPassenger = Notification.Name(rawValue: "com.12306ForMac.App.DidCheckPassenger") 28 | 29 | public static let DidAutoLogin = Notification.Name(rawValue: "com.12306ForMac.App.DidAutoLogin") 30 | 31 | public static let DidAutoSubmit = Notification.Name(rawValue: "com.12306ForMac.App.DidAutoSubmit") 32 | public static let DidAutoSubmitWithoutRandCode = Notification.Name(rawValue: "com.12306ForMac.App.DidAutoSubmitWithoutRandCode") 33 | 34 | public static let DidAddDefaultPassenger = Notification.Name(rawValue: "com.12306ForMac.App.DidAddDefaultPassenger") 35 | 36 | public static let DidDamaGetBalance = Notification.Name(rawValue: "com.12306ForMac.App.DidDamaGetBalance") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /12306ForMac/Utilities/NotifySpeaker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotifySpeaker.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 16/9/17. 6 | // Copyright © 2016年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | class NotifySpeaker { 13 | fileprivate static let sharedManager = NotifySpeaker() 14 | class var sharedInstance: NotifySpeaker { 15 | return sharedManager 16 | } 17 | 18 | fileprivate let speachSynth: NSSpeechSynthesizer 19 | 20 | fileprivate init () { 21 | speachSynth = NSSpeechSynthesizer() 22 | let isSuccess = speachSynth.setVoice("com.apple.speech.synthesis.voice.mei-jia") 23 | if !isSuccess { 24 | speachSynth.setVoice("com.apple.speech.synthesis.voice.ting-ting") 25 | } 26 | } 27 | 28 | func notify() { 29 | if GeneralPreferenceManager.sharedInstance.isNotifyTicket { 30 | speachSynth.startSpeaking(GeneralPreferenceManager.sharedInstance.notifyStr) 31 | } 32 | } 33 | 34 | func stopNotify(){ 35 | speachSynth.stopSpeaking() 36 | } 37 | 38 | func notifyLogin() { 39 | if GeneralPreferenceManager.sharedInstance.isNotifyLogin { 40 | speachSynth.startSpeaking(GeneralPreferenceManager.sharedInstance.notifyLoginStr) 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /12306ForMac/Utilities/QueryDefaultManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaultManager.swift 3 | // Train12306 4 | // 5 | // Created by fancymax on 15/11/26. 6 | // Copyright © 2015年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class QueryDefaultManager { 12 | static let sharedInstance = QueryDefaultManager() 13 | 14 | private let userNameKey = "userName" 15 | private let userPasswordKey = "userPassword" 16 | private let fromStationKey = "fromStation" 17 | private let toStationKey = "toStation" 18 | private let queryDateKey = "queryDate" 19 | private let selectedPassenger = "selectedPassenger" 20 | private let allSelectedDate = "allSelectedDate" 21 | private let ticketTaskManager = "ticketTaskManager" 22 | private let userDefaults = UserDefaults.standard 23 | 24 | private init() 25 | { 26 | registerUserDefault() 27 | } 28 | 29 | private func registerUserDefault() 30 | { 31 | let firstDefault = [fromStationKey: "深圳", 32 | toStationKey:"上海",queryDateKey:Date()] as [String : Any] 33 | userDefaults.register(defaults: firstDefault) 34 | } 35 | 36 | var lastUserName:String?{ 37 | get{ 38 | return userDefaults.object(forKey: userNameKey) as? String 39 | } 40 | set(newValue){ 41 | userDefaults.set(newValue, forKey: userNameKey) 42 | } 43 | } 44 | 45 | var lastUserPassword:String?{ 46 | get{ 47 | return userDefaults.object(forKey: userPasswordKey) as? String 48 | } 49 | set(newValue){ 50 | userDefaults.set(newValue, forKey: userPasswordKey) 51 | } 52 | } 53 | 54 | var lastFromStation:String{ 55 | get{ 56 | return userDefaults.object(forKey: fromStationKey) as! String 57 | } 58 | set(newValue){ 59 | userDefaults.set(newValue, forKey: fromStationKey) 60 | } 61 | } 62 | 63 | var lastToStation:String{ 64 | get{ 65 | return userDefaults.object(forKey: toStationKey) as! String 66 | } 67 | set(newValue){ 68 | userDefaults.set(newValue, forKey: toStationKey) 69 | } 70 | } 71 | 72 | var lastQueryDate:Date{ 73 | get{ 74 | return userDefaults.object(forKey: queryDateKey) as! Date 75 | } 76 | set(newValue){ 77 | userDefaults.set(newValue, forKey: queryDateKey) 78 | } 79 | } 80 | 81 | var lastSelectedPassenger:String? { 82 | get { 83 | return userDefaults.object(forKey: selectedPassenger) as? String 84 | } 85 | set(newValue) { 86 | userDefaults.set(newValue,forKey: selectedPassenger) 87 | } 88 | } 89 | 90 | var lastAllSelectedDates:[Date]? { 91 | get { 92 | return userDefaults.array(forKey: allSelectedDate) as? [Date] 93 | } 94 | set(newValue) { 95 | userDefaults.set(newValue,forKey: allSelectedDate) 96 | } 97 | } 98 | 99 | var lastTicketTaskManager:String? { 100 | get { 101 | return userDefaults.object(forKey: ticketTaskManager) as? String 102 | } 103 | set(newValue) { 104 | userDefaults.set(newValue,forKey: ticketTaskManager) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /12306ForMac/Utilities/ReminderManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReminderManager.swift 3 | // 12306ForMac 4 | // 5 | // Created by fancymax on 2/24/2017. 6 | // Copyright © 2017年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import EventKit 11 | 12 | class ReminderManager: NSObject { 13 | 14 | private let eventStore:EKEventStore 15 | private var isAccessToEventStoreGranted:Bool 16 | 17 | fileprivate static let sharedManager = ReminderManager() 18 | class var sharedInstance: ReminderManager { 19 | return sharedManager 20 | } 21 | 22 | private override init () { 23 | isAccessToEventStoreGranted = false 24 | eventStore = EKEventStore() 25 | } 26 | 27 | @discardableResult 28 | func updateAuthorizationStatus()->Bool { 29 | switch EKEventStore.authorizationStatus(for: .reminder) { 30 | case .authorized: 31 | self.isAccessToEventStoreGranted = true 32 | return true 33 | case .notDetermined: 34 | self.eventStore.requestAccess(to: .reminder, completion: {[unowned self] granted,error in 35 | DispatchQueue.main.async { 36 | self.isAccessToEventStoreGranted = granted 37 | } 38 | }) 39 | return false 40 | case .restricted, .denied: 41 | isAccessToEventStoreGranted = false 42 | return false 43 | } 44 | } 45 | 46 | func createReminder(_ title:String, startDate:Date) { 47 | if !isAccessToEventStoreGranted { 48 | return 49 | } 50 | 51 | let reminder = EKReminder(eventStore: self.eventStore) 52 | 53 | reminder.calendar = eventStore.defaultCalendarForNewReminders() 54 | reminder.title = title 55 | 56 | var date = Date(timeInterval: 5, since: Date()) 57 | var alarm = EKAlarm(absoluteDate: date) 58 | reminder.addAlarm(alarm) 59 | 60 | date = Date(timeInterval: 10, since: Date()) 61 | alarm = EKAlarm(absoluteDate: date) 62 | reminder.addAlarm(alarm) 63 | 64 | date = Date(timeInterval: 15, since: Date()) 65 | alarm = EKAlarm(absoluteDate: date) 66 | reminder.addAlarm(alarm) 67 | 68 | try! eventStore.save(reminder, commit: true) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /12306ForMac/Utilities/SwiftyRegex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftRegex.swift 3 | // Train12306 4 | // 5 | // Created by fancymax on 15/7/31. 6 | // Copyright (c) 2015年 fancy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Regex { 12 | let internalExpression: NSRegularExpression? 13 | let pattern: String 14 | 15 | init(_ pattern: String) { 16 | self.pattern = pattern 17 | do { 18 | self.internalExpression = try NSRegularExpression(pattern: pattern, options: .caseInsensitive) 19 | } catch _ as NSError { 20 | self.internalExpression = nil 21 | } 22 | } 23 | 24 | func getMatches(_ input: String) -> [[String]]? { 25 | var res = [[String]]() 26 | let myRange = NSMakeRange(0, input.characters.count) 27 | if let matches = self.internalExpression?.matches(in: input, options: [], range:myRange) 28 | { 29 | for match in matches 30 | { 31 | var groupMatch = [String]() 32 | for i in 1.. 0{ 41 | return res 42 | } 43 | else{ 44 | return nil 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" == 4.0 2 | github "ccgus/fmdb" == 2.6.2 3 | github "mxcl/OMGHTTPURLRQ" == 3.2.2 4 | github "mxcl/PromiseKit" == 4.1.0 5 | github "DaveWoodCom/XCGLogger" == 5.0.5 6 | github "shpakovski/MASPreferences" == 1.1.4 7 | github "SwiftyJSON/SwiftyJSON" -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" "4.0.0" 2 | github "DaveWoodCom/XCGLogger" "5.0.5" 3 | github "SwiftyJSON/SwiftyJSON" "4.0.0" 4 | github "ccgus/fmdb" "2.6.2" 5 | github "mxcl/OMGHTTPURLRQ" "3.2.2" 6 | github "mxcl/PromiseKit" "4.1.0" 7 | github "shpakovski/MASPreferences" "1.1.4" 8 | -------------------------------------------------------------------------------- /DJProgressHUD/DJActivityIndicator.h: -------------------------------------------------------------------------------- 1 | // 2 | // DJProgressIndicator.h 3 | // Playground 4 | // 5 | // Created by Daniel Jackson on 5/11/14. 6 | // Copyright (c) 2014 Daniel Jackson. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DJActivityIndicator : NSView 12 | 13 | @property BOOL isAnimating; 14 | 15 | - (void)setColor:(NSColor *)value; 16 | - (void)setBackgroundColor:(NSColor *)value; 17 | 18 | - (void)stopAnimation:(id)sender; 19 | - (void)startAnimation:(id)sender; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /DJProgressHUD/DJActivityIndicator.m: -------------------------------------------------------------------------------- 1 | // 2 | // DJProgressIndicator.m 3 | // Playground 4 | // 5 | // Created by Daniel Jackson on 5/11/14. 6 | // Copyright (c) 2014 Daniel Jackson. All rights reserved. 7 | // 8 | 9 | #import "DJActivityIndicator.h" 10 | 11 | #define kAlphaWhenStopped 0.15 12 | #define kFadeMultiplier 0.85 13 | 14 | @interface DJActivityIndicator () 15 | { 16 | int position; 17 | NSMutableArray* finColors; 18 | 19 | BOOL isFadingOut; 20 | NSTimer* animationTimer; 21 | 22 | NSColor* foreColor; 23 | NSColor* backColor; 24 | } 25 | @end 26 | 27 | @implementation DJActivityIndicator 28 | 29 | - (id)initWithFrame:(NSRect)frame 30 | { 31 | self = [super initWithFrame:frame]; 32 | if (self) { 33 | position = 0; 34 | int numFins = 12; 35 | 36 | finColors = [[NSMutableArray alloc] initWithCapacity:numFins]; 37 | 38 | _isAnimating = NO; 39 | isFadingOut = NO; 40 | 41 | foreColor = [NSColor blackColor]; 42 | backColor = [NSColor clearColor]; 43 | 44 | for(int i=0; i= size.height) 59 | theMaxSize = size.height; 60 | else 61 | theMaxSize = size.width; 62 | 63 | [backColor set]; 64 | [NSBezierPath fillRect:[self bounds]]; 65 | 66 | CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; 67 | [NSGraphicsContext saveGraphicsState]; 68 | 69 | CGContextTranslateCTM(currentContext,[self bounds].size.width/2,[self bounds].size.height/2); 70 | 71 | NSBezierPath *path = [[NSBezierPath alloc] init]; 72 | CGFloat lineWidth = 0.0859375 * theMaxSize; 73 | CGFloat lineStart = 0.234375 * theMaxSize; 74 | CGFloat lineEnd = 0.421875 * theMaxSize; 75 | [path setLineWidth:lineWidth]; 76 | [path setLineCapStyle:NSRoundLineCapStyle]; 77 | [path moveToPoint:NSMakePoint(0,lineStart)]; 78 | [path lineToPoint:NSMakePoint(0,lineEnd)]; 79 | 80 | for (int i=0; i 0) { 136 | position--; 137 | } 138 | else { 139 | position = (int)finColors.count - 1; 140 | } 141 | 142 | CGFloat minAlpha = kAlphaWhenStopped; 143 | for (int i=0; i 0.01) { 155 | done = NO; 156 | break; 157 | } 158 | } 159 | if (done) { 160 | [self actuallyStopAnimation]; 161 | } 162 | } 163 | else { 164 | finColors[position] = foreColor; 165 | } 166 | 167 | [self setNeedsDisplay:YES]; 168 | 169 | 170 | } 171 | 172 | - (void)actuallyStartAnimation 173 | { 174 | [self actuallyStopAnimation]; 175 | 176 | _isAnimating = YES; 177 | isFadingOut = NO; 178 | 179 | position = 1; 180 | 181 | animationTimer = [NSTimer timerWithTimeInterval:(NSTimeInterval)0.05 182 | target:self 183 | selector:@selector(updateFrame:) 184 | userInfo:nil 185 | repeats:YES]; 186 | 187 | [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSRunLoopCommonModes]; 188 | [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSDefaultRunLoopMode]; 189 | [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSEventTrackingRunLoopMode]; 190 | } 191 | 192 | - (void)actuallyStopAnimation 193 | { 194 | _isAnimating = NO; 195 | isFadingOut = NO; 196 | 197 | if (animationTimer) { 198 | // we were using timer-based animation 199 | [animationTimer invalidate]; 200 | animationTimer = nil; 201 | } 202 | //[self setNeedsDisplay:YES]; 203 | } 204 | 205 | 206 | 207 | 208 | @end 209 | -------------------------------------------------------------------------------- /DJProgressHUD/DJLayerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DJLayerView.h 3 | // Playground 4 | // 5 | // Created by fancymax on 16/10/24. 6 | // Copyright © 2016年 Daniel Jackson. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DJLayerView : NSView 12 | 13 | +(void)showStatus:(NSString*)status FromView:(NSView *)view; 14 | +(void)dismiss; 15 | 16 | // Customization 17 | #define pMaxWidth1 250 18 | #define pMaxHeight1 200 19 | 20 | //General Popup Values 21 | @property (nonatomic) CGVector pOffset; 22 | @property (nonatomic) CGFloat pAlpha; 23 | 24 | //Padding 25 | @property (nonatomic) CGFloat pPadding; 26 | 27 | @property (nonatomic) CGSize indicatorSize; 28 | @property (nonatomic) CGVector indicatorOffset; 29 | @property (nonatomic) CGSize labelSize; 30 | @property (nonatomic) CGVector labelOffset; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /DJProgressHUD/DJLayerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DJLayerView.m 3 | // Playground 4 | // 5 | // Created by fancymax on 16/10/24. 6 | // Copyright © 2016年 Daniel Jackson. All rights reserved. 7 | // 8 | 9 | #import "DJLayerView.h" 10 | #import "DJActivityIndicator.h" 11 | 12 | @interface DJLayerView(){ 13 | NSView* parentView; 14 | 15 | CGSize pSize; //This is set automatically based on the content 16 | DJActivityIndicator* activityIndicator; 17 | NSTextField* label; 18 | } 19 | 20 | @end 21 | 22 | @implementation DJLayerView 23 | 24 | +(void)showStatus:(NSString *)status FromView:(NSView *)parentView{ 25 | [[self instance] showStatus:status FromView:parentView]; 26 | } 27 | 28 | +(void)dismiss { 29 | [[self instance] finishHideView]; 30 | } 31 | 32 | -(void)finishHideView 33 | { 34 | [self removeFromSuperview]; 35 | parentView = nil; 36 | 37 | [activityIndicator stopAnimation:nil]; 38 | } 39 | 40 | -(void)showStatus:(NSString*)status FromView:(NSView *)view { 41 | parentView = view; 42 | label.stringValue = status; 43 | [activityIndicator startAnimation:nil]; 44 | 45 | if (!self.superview) { 46 | [parentView addSubview:self]; 47 | } 48 | 49 | [self updateLayout]; 50 | 51 | CGColorRef bgcolor = CGColorCreateGenericRGB(0.05, 0.05, 0.05, 0.8); 52 | self.layer.backgroundColor = bgcolor; 53 | self.layer.cornerRadius = 15.0; 54 | [self setFrame:[self getCenterWithinRect:parentView.frame scale:1]]; 55 | } 56 | 57 | - (NSRect)getCenterWithinRect:(NSRect)parentFrame scale:(CGFloat)scale 58 | { 59 | NSRect result; 60 | CGFloat newWidth = pSize.width*scale; 61 | CGFloat newHeight = pSize.height*scale; 62 | result.origin.x = parentFrame.size.width/2 - newWidth/2 + _pOffset.dx; 63 | result.origin.y = parentFrame.size.height/2 - newHeight/2 + _pOffset.dy; 64 | result.size.width = newWidth; 65 | result.size.height = newHeight; 66 | 67 | return result; 68 | } 69 | 70 | - (void)updateLayout 71 | { 72 | CGSize maxContentSize = CGSizeMake(pMaxWidth1-(_pPadding*2), pMaxHeight1-(_pPadding*2)); 73 | CGSize minContentSize = CGSizeMake(_indicatorSize.width, _indicatorSize.height); 74 | 75 | CGFloat stringWidth = [label.stringValue sizeWithAttributes:@{ NSFontAttributeName : label.font }].width + 5; 76 | float stringHeight = [self heightForString:label.stringValue font:label.font width:maxContentSize.width] + 8; 77 | 78 | if(label.stringValue == nil || label.stringValue.length == 0) 79 | stringHeight = 0; 80 | 81 | stringWidth = (stringWidth > minContentSize.width) ? stringWidth : minContentSize.width; 82 | if(stringWidth > maxContentSize.width) 83 | stringWidth = maxContentSize.width; 84 | 85 | CGFloat maxStringHeight = maxContentSize.height-_indicatorSize.height-(_pPadding+(_pPadding/2)); 86 | stringHeight = (stringHeight > maxStringHeight) ? maxStringHeight : stringHeight; 87 | 88 | CGFloat popupWidth = stringWidth+(_pPadding*2); 89 | 90 | CGFloat lW = stringWidth; 91 | CGFloat lH = stringHeight; 92 | CGFloat lX = _pPadding; 93 | CGFloat lY = (stringHeight == 0) ? 0 : _pPadding; 94 | [label setFrame:NSMakeRect(lX, lY, lW, lH)]; 95 | 96 | CGFloat spaceBetween = (stringHeight != 0) ? _pPadding/3 : _pPadding; 97 | 98 | CGFloat iW = _indicatorSize.width; 99 | CGFloat iH = _indicatorSize.height; 100 | CGFloat iX = ((lW+(_pPadding*2))/2)-(iW/2); //center it 101 | CGFloat iY = lY+lH+(spaceBetween); 102 | NSRect indicatorRect = NSMakeRect(iX, iY, iW, iH); 103 | activityIndicator.frame = indicatorRect; 104 | 105 | CGFloat spaceOnTop = (stringHeight != 0) ? _pPadding/3 : 0; 106 | 107 | [activityIndicator setColor:[NSColor whiteColor]]; 108 | 109 | pSize.width = popupWidth; 110 | pSize.height = iY+iH+_pPadding+spaceOnTop;//+(_pPadding/2); 111 | 112 | [self setAutoresizesSubviews:YES]; 113 | } 114 | 115 | -(CGFloat) heightForString:(NSString *)myString font:(NSFont*) myFont width:(CGFloat)myWidth 116 | { 117 | NSTextStorage *textStorage = [[NSTextStorage alloc] initWithString:myString]; 118 | NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(myWidth, FLT_MAX)]; 119 | ; 120 | NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; 121 | [layoutManager addTextContainer:textContainer]; 122 | [textStorage addLayoutManager:layoutManager]; 123 | [textStorage addAttribute:NSFontAttributeName value:myFont 124 | range:NSMakeRange(0, [textStorage length])]; 125 | [textContainer setLineFragmentPadding:0.0]; 126 | 127 | (void) [layoutManager glyphRangeForTextContainer:textContainer]; 128 | return [layoutManager 129 | usedRectForTextContainer:textContainer].size.height; 130 | } 131 | 132 | - (void)initializePopup 133 | { 134 | [self setWantsLayer:YES]; 135 | 136 | self.autoresizingMask = NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin; 137 | 138 | activityIndicator = [[DJActivityIndicator alloc] init]; 139 | label = [[NSTextField alloc] init]; 140 | 141 | [self addSubview:label]; 142 | [self addSubview:activityIndicator]; 143 | 144 | //----DEFAULT VALUES---- 145 | 146 | _pOffset = CGVectorMake(0, 0); 147 | _pAlpha = 0.9; 148 | _pPadding = 10; 149 | 150 | _indicatorSize = CGSizeMake(30, 30); 151 | _indicatorOffset = CGVectorMake(0, 0); 152 | 153 | [label setBezeled:NO]; 154 | [label setDrawsBackground:NO]; 155 | [label setEditable:NO]; 156 | [label setSelectable:NO]; 157 | 158 | label.font = [NSFont systemFontOfSize:13.0]; 159 | [label setTextColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.85]]; 160 | } 161 | 162 | + (DJLayerView *) instance 163 | { 164 | static dispatch_once_t once; 165 | static DJLayerView *sharedView; 166 | dispatch_once(&once, ^ { 167 | sharedView = [[self alloc] init]; 168 | [sharedView initializePopup]; 169 | }); 170 | 171 | return sharedView; 172 | } 173 | 174 | @end 175 | -------------------------------------------------------------------------------- /DJProgressHUD/DJTipHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // CPProgressView.h 3 | // Cloud Play OSX 4 | // 5 | // Created by Daniel Jackson on 4/22/14. 6 | // Copyright (c) 2014 Daniel Jackson. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | @interface DJTipHUD : NSView 14 | 15 | + (void)showStatus:(NSString*)status FromView:(NSView*)view; 16 | 17 | @property (nonatomic, readonly) BOOL displaying; 18 | 19 | // Customization 20 | #define pMaxWidth1 250 21 | #define pMaxHeight1 200 22 | 23 | //General Popup Values 24 | @property (nonatomic) CGVector pOffset; 25 | @property (nonatomic) CGFloat pAlpha; 26 | 27 | //Padding 28 | @property (nonatomic) CGFloat pPadding; 29 | 30 | @property (nonatomic) CGSize indicatorSize; 31 | @property (nonatomic) CGVector indicatorOffset; 32 | @property (nonatomic) CGSize labelSize; 33 | @property (nonatomic) CGVector labelOffset; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /DJProgressHUD/DJTipHUD.m: -------------------------------------------------------------------------------- 1 | // 2 | // CPProgressView.m 3 | // Cloud Play OSX 4 | // 5 | // Created by Daniel Jackson on 4/22/14. 6 | // Copyright (c) 2014 Daniel Jackson. All rights reserved. 7 | // 8 | 9 | #import "DJTipHUD.h" 10 | 11 | typedef void (^CompletionHander)(void); 12 | 13 | @interface DJTipHUD () 14 | { 15 | NSView* parentView; 16 | CGSize pSize; //This is set automatically based on the content 17 | NSTextField* label; 18 | } 19 | 20 | @end 21 | 22 | @implementation DJTipHUD 23 | 24 | #pragma mark - 25 | #pragma mark Class Methods 26 | 27 | + (void)showStatus:(NSString*)status FromView:(NSView*)view 28 | { 29 | [[self instance] showStatus:status FromView:view]; 30 | } 31 | 32 | #pragma mark - 33 | #pragma mark Master Methods 34 | 35 | - (void)showStatus:(NSString*)status FromView:(NSView*)view 36 | { 37 | if (_displaying) { 38 | return; 39 | } 40 | 41 | parentView = view; 42 | label.stringValue = status; 43 | 44 | if(!self.superview) { 45 | [parentView addSubview:self]; 46 | _displaying = true; 47 | } 48 | 49 | CGColorRef bgcolor = CGColorCreateGenericRGB(0.05, 0.05, 0.05, 0.8); 50 | self.layer.backgroundColor = bgcolor; 51 | self.layer.cornerRadius = 15.0; 52 | 53 | [self updateLayout]; 54 | NSRect size = [self getCenterWithinRect:parentView.frame scale:1.0]; 55 | [self setFrame:size]; 56 | // [self setFrame:[self getCenterWithinRect:parentView.frame scale:1.0]]; 57 | 58 | NSInteger interval = 2; 59 | if ([status length] >= 20) { 60 | interval = 4; 61 | } 62 | if ([status length] >= 30) { 63 | interval = 6; 64 | } 65 | [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(finishHideView) userInfo:nil repeats:NO]; 66 | } 67 | 68 | #pragma mark - 69 | #pragma mark Instance Methods 70 | 71 | -(void)finishHideView 72 | { 73 | [self removeFromSuperview]; 74 | parentView = nil; 75 | _displaying = false; 76 | } 77 | 78 | #pragma mark - 79 | #pragma mark Laying It Out 80 | 81 | - (void)updateLayout 82 | { 83 | CGSize maxContentSize = CGSizeMake(pMaxWidth1-(_pPadding*2), pMaxHeight1-(_pPadding*2)); 84 | CGSize minContentSize = CGSizeMake(_indicatorSize.width, _indicatorSize.height); 85 | 86 | CGFloat stringWidth = [label.stringValue sizeWithAttributes:@{ NSFontAttributeName : label.font }].width + 5; 87 | float stringHeight = [self heightForString:label.stringValue font:label.font width:maxContentSize.width] + 8; 88 | 89 | if(label.stringValue == nil || label.stringValue.length == 0) 90 | stringHeight = 0; 91 | 92 | stringWidth = (stringWidth > minContentSize.width) ? stringWidth : minContentSize.width; 93 | if(stringWidth > maxContentSize.width) 94 | stringWidth = maxContentSize.width; 95 | 96 | CGFloat maxStringHeight = maxContentSize.height-_indicatorSize.height-(_pPadding+(_pPadding/2)); 97 | stringHeight = (stringHeight > maxStringHeight) ? maxStringHeight : stringHeight; 98 | 99 | CGFloat popupWidth = stringWidth+(_pPadding*2); 100 | 101 | CGFloat lW = stringWidth; 102 | CGFloat lH = stringHeight; 103 | CGFloat lX = _pPadding; 104 | CGFloat lY = (stringHeight == 0) ? 0 : _pPadding; 105 | [label setFrame:NSMakeRect(lX, lY, lW, lH)]; 106 | 107 | CGFloat spaceBetween = (stringHeight != 0) ? _pPadding/3 : _pPadding; 108 | 109 | // CGFloat iW = _indicatorSize.width; 110 | CGFloat iH = _indicatorSize.height; 111 | // CGFloat iX = ((lW+(_pPadding*2))/2)-(iW/2); //center it 112 | CGFloat iY = lY+lH+(spaceBetween); 113 | 114 | CGFloat spaceOnTop = (stringHeight != 0) ? _pPadding/3 : 0; 115 | 116 | pSize.width = popupWidth; 117 | pSize.height = iY+iH+_pPadding+spaceOnTop;//+(_pPadding/2); 118 | 119 | [self setAutoresizesSubviews:YES]; 120 | } 121 | 122 | #pragma mark - 123 | #pragma mark Other 124 | 125 | -(CGFloat) heightForString:(NSString *)myString font:(NSFont*) myFont width:(CGFloat)myWidth 126 | { 127 | NSTextStorage *textStorage = [[NSTextStorage alloc] initWithString:myString]; 128 | NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(myWidth, FLT_MAX)]; 129 | ; 130 | NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; 131 | [layoutManager addTextContainer:textContainer]; 132 | [textStorage addLayoutManager:layoutManager]; 133 | [textStorage addAttribute:NSFontAttributeName value:myFont 134 | range:NSMakeRange(0, [textStorage length])]; 135 | [textContainer setLineFragmentPadding:0.0]; 136 | 137 | (void) [layoutManager glyphRangeForTextContainer:textContainer]; 138 | return [layoutManager 139 | usedRectForTextContainer:textContainer].size.height; 140 | } 141 | 142 | - (NSRect)getCenterWithinRect:(NSRect)parentFrame scale:(CGFloat)scale 143 | { 144 | NSRect result; 145 | CGFloat newWidth = pSize.width*scale; 146 | CGFloat newHeight = pSize.height*scale; 147 | result.origin.x = parentFrame.size.width/2 - newWidth/2 + _pOffset.dx; 148 | result.origin.y = parentFrame.size.height/2 - newHeight/2 + _pOffset.dy; 149 | result.size.width = newWidth; 150 | result.size.height = newHeight; 151 | 152 | return result; 153 | } 154 | 155 | #pragma mark - 156 | 157 | - (void)initializePopup 158 | { 159 | [self setWantsLayer:YES]; 160 | 161 | self.autoresizingMask = NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin; 162 | 163 | label = [[NSTextField alloc] init]; 164 | 165 | 166 | [self addSubview:label]; 167 | 168 | //----DEFAULT VALUES---- 169 | 170 | _pOffset = CGVectorMake(0, 0); 171 | _pAlpha = 0.9; 172 | _pPadding = 10; 173 | 174 | _indicatorSize = CGSizeMake(0, 0); 175 | _indicatorOffset = CGVectorMake(0, 0); 176 | 177 | [label setBezeled:NO]; 178 | [label setDrawsBackground:NO]; 179 | [label setEditable:NO]; 180 | [label setSelectable:NO]; 181 | 182 | label.font = [NSFont systemFontOfSize:13.0]; 183 | [label setTextColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.85]]; 184 | } 185 | 186 | + (DJTipHUD *) instance 187 | { 188 | static dispatch_once_t once; 189 | static DJTipHUD *sharedView; 190 | dispatch_once(&once, ^ { 191 | sharedView = [[self alloc] init]; 192 | [sharedView initializePopup]; 193 | }); 194 | 195 | return sharedView; 196 | } 197 | 198 | @end 199 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Max Lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 12306ForMac 2 | 3 | 仅做学习参考,随着12306接口的变更可能无法正常使用。 4 | 5 | Mac版12306 订票/捡票 助手。 6 | 7 | 以前要么开Windows虚拟机,要么使用官方Web,现在可以使用12306ForMac订票助手。 8 | 9 | 注意系统要求 **OS X10.11** 以上 10 | 11 | ![demo](screenshot/12306ForMac.jpg) 12 | 13 | # 开发 14 | 15 | 1. OS X 10.13/Xcode 9.0/Swift 3.2/brew 16 | 2. $ brew install carthage 17 | 3. $ git clone --recursive https://github.com/fancymax/12306ForMac.git 18 | 5. $ cd 12306ForMac 19 | 4. $ carthage update --platform macOS 20 | 21 | # 感谢 22 | 23 | 本项目基于 Alamofire、PromiseKit、FMDB、MASPreferences等进行开发,在此表示感谢。 24 | 25 | -------------------------------------------------------------------------------- /screenshot/12306ForMac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/screenshot/12306ForMac.jpg -------------------------------------------------------------------------------- /screenshot/donate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancymax/12306ForMac/ff69af82c3d345f86b72e0b6ccedd6c3738d4456/screenshot/donate.png --------------------------------------------------------------------------------