diff --git a/.github/workflows/macOS-deploy.yml b/.github/workflows/macOS-deploy.yml new file mode 100644 index 0000000..e6ec46a --- /dev/null +++ b/.github/workflows/macOS-deploy.yml @@ -0,0 +1,36 @@ +on: + push: + tags: + - 'v*' + +name: macOS Deploy + +jobs: + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: xcodebuild + - name: Zip + run: cd build/Release && zip SrunBar.app.zip -r SrunBar.app + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + - name: Upload Release + id: upload-release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./build/Release/SrunBar.app.zip + asset_name: SrunBar.app.zip + asset_content_type: application/zip \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb31fa3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# idea +.idea/ + +# Mac +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control +# +# Pods/ + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..41969db --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 vouv + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cd2bb7 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# SrunBar + +北理工校园网客户端 for macOS + +## Related Projects + +- 命令行登录工具: [srun](https://github.com/vouv/srun) + +## 效果图 + +![demo](./doc/demo.jpg) diff --git a/SrunBar.xcodeproj/project.pbxproj b/SrunBar.xcodeproj/project.pbxproj new file mode 100644 index 0000000..48764ab --- /dev/null +++ b/SrunBar.xcodeproj/project.pbxproj @@ -0,0 +1,585 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0E4165442593AC4E00B48231 /* SrunAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4165432593AC4E00B48231 /* SrunAPI.swift */; }; + 0E4165492595114200B48231 /* Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4165482595114200B48231 /* Hash.swift */; }; + 0E76EEBE2597103900A24BC8 /* model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E76EEBD2597103900A24BC8 /* model.swift */; }; + 0E76EECF2597175300A24BC8 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E76EECE2597175300A24BC8 /* Request.swift */; }; + 0E76EEEF2597AEB700A24BC8 /* AboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E76EEEE2597AEB700A24BC8 /* AboutWindow.swift */; }; + 0E76EEF72597AFC900A24BC8 /* AboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E76EEF62597AFC900A24BC8 /* AboutWindow.xib */; }; + A21862A21BCDEA2E00770E87 /* ConfigWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21862A01BCDEA2E00770E87 /* ConfigWindow.swift */; }; + A21862A31BCDEA2E00770E87 /* ConfigWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = A21862A11BCDEA2E00770E87 /* ConfigWindow.xib */; }; + A2B4247D1BCDE3D300887CB2 /* InfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2B4247C1BCDE3D300887CB2 /* InfoView.swift */; }; + A2D582281BCAAF37006A464B /* StatusMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2D582271BCAAF37006A464B /* StatusMenuController.swift */; }; + A2DEB14E1BC9980E004AAEB3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2DEB14D1BC9980E004AAEB3 /* AppDelegate.swift */; }; + A2DEB1501BC9980E004AAEB3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A2DEB14F1BC9980E004AAEB3 /* Assets.xcassets */; }; + A2DEB1531BC9980E004AAEB3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = A2DEB1511BC9980E004AAEB3 /* MainMenu.xib */; }; + A2DEB15E1BC9980E004AAEB3 /* SrunBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2DEB15D1BC9980E004AAEB3 /* SrunBarTests.swift */; }; + A2DEB1691BC9980E004AAEB3 /* SrunBarUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2DEB1681BC9980E004AAEB3 /* SrunBarUITests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + A2DEB15A1BC9980E004AAEB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A2DEB1421BC9980E004AAEB3 /* Project object */; + proxyType = 1; + remoteGlobalIDString = A2DEB1491BC9980E004AAEB3; + remoteInfo = WeatherBar; + }; + A2DEB1651BC9980E004AAEB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A2DEB1421BC9980E004AAEB3 /* Project object */; + proxyType = 1; + remoteGlobalIDString = A2DEB1491BC9980E004AAEB3; + remoteInfo = WeatherBar; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0E4165432593AC4E00B48231 /* SrunAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SrunAPI.swift; sourceTree = ""; }; + 0E4165482595114200B48231 /* Hash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hash.swift; sourceTree = ""; }; + 0E76EEBD2597103900A24BC8 /* model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = model.swift; sourceTree = ""; }; + 0E76EECE2597175300A24BC8 /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; }; + 0E76EEEE2597AEB700A24BC8 /* AboutWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindow.swift; sourceTree = ""; }; + 0E76EEF62597AFC900A24BC8 /* AboutWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AboutWindow.xib; sourceTree = ""; }; + A21862A01BCDEA2E00770E87 /* ConfigWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigWindow.swift; sourceTree = ""; }; + A21862A11BCDEA2E00770E87 /* ConfigWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ConfigWindow.xib; sourceTree = ""; }; + A2B4247C1BCDE3D300887CB2 /* InfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoView.swift; sourceTree = ""; }; + A2D582271BCAAF37006A464B /* StatusMenuController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusMenuController.swift; sourceTree = ""; }; + A2DEB14A1BC9980E004AAEB3 /* SrunBar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SrunBar.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A2DEB14D1BC9980E004AAEB3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + A2DEB14F1BC9980E004AAEB3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + A2DEB1521BC9980E004AAEB3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + A2DEB1541BC9980E004AAEB3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A2DEB1591BC9980E004AAEB3 /* SrunBarTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SrunBarTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A2DEB15D1BC9980E004AAEB3 /* SrunBarTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SrunBarTests.swift; sourceTree = ""; }; + A2DEB15F1BC9980E004AAEB3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A2DEB1641BC9980E004AAEB3 /* SrunBarUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SrunBarUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A2DEB1681BC9980E004AAEB3 /* SrunBarUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SrunBarUITests.swift; sourceTree = ""; }; + A2DEB16A1BC9980E004AAEB3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A2DEB1471BC9980E004AAEB3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2DEB1561BC9980E004AAEB3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2DEB1611BC9980E004AAEB3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0E76EEBC2597102C00A24BC8 /* srun */ = { + isa = PBXGroup; + children = ( + 0E4165432593AC4E00B48231 /* SrunAPI.swift */, + 0E4165482595114200B48231 /* Hash.swift */, + 0E76EEBD2597103900A24BC8 /* model.swift */, + 0E76EECE2597175300A24BC8 /* Request.swift */, + ); + path = srun; + sourceTree = ""; + }; + A2DEB1411BC9980E004AAEB3 = { + isa = PBXGroup; + children = ( + A2DEB14C1BC9980E004AAEB3 /* SrunBar */, + A2DEB15C1BC9980E004AAEB3 /* SrunBarTests */, + A2DEB1671BC9980E004AAEB3 /* SrunBarUITests */, + A2DEB14B1BC9980E004AAEB3 /* Products */, + ); + sourceTree = ""; + }; + A2DEB14B1BC9980E004AAEB3 /* Products */ = { + isa = PBXGroup; + children = ( + A2DEB14A1BC9980E004AAEB3 /* SrunBar.app */, + A2DEB1591BC9980E004AAEB3 /* SrunBarTests.xctest */, + A2DEB1641BC9980E004AAEB3 /* SrunBarUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + A2DEB14C1BC9980E004AAEB3 /* SrunBar */ = { + isa = PBXGroup; + children = ( + A2DEB14D1BC9980E004AAEB3 /* AppDelegate.swift */, + A2D582271BCAAF37006A464B /* StatusMenuController.swift */, + A2B4247C1BCDE3D300887CB2 /* InfoView.swift */, + 0E76EEEE2597AEB700A24BC8 /* AboutWindow.swift */, + 0E76EEBC2597102C00A24BC8 /* srun */, + A21862A01BCDEA2E00770E87 /* ConfigWindow.swift */, + A2DEB1511BC9980E004AAEB3 /* MainMenu.xib */, + A21862A11BCDEA2E00770E87 /* ConfigWindow.xib */, + 0E76EEF62597AFC900A24BC8 /* AboutWindow.xib */, + A2DEB14F1BC9980E004AAEB3 /* Assets.xcassets */, + A2DEB1541BC9980E004AAEB3 /* Info.plist */, + ); + path = SrunBar; + sourceTree = ""; + }; + A2DEB15C1BC9980E004AAEB3 /* SrunBarTests */ = { + isa = PBXGroup; + children = ( + A2DEB15D1BC9980E004AAEB3 /* SrunBarTests.swift */, + A2DEB15F1BC9980E004AAEB3 /* Info.plist */, + ); + path = SrunBarTests; + sourceTree = ""; + }; + A2DEB1671BC9980E004AAEB3 /* SrunBarUITests */ = { + isa = PBXGroup; + children = ( + A2DEB1681BC9980E004AAEB3 /* SrunBarUITests.swift */, + A2DEB16A1BC9980E004AAEB3 /* Info.plist */, + ); + path = SrunBarUITests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A2DEB1491BC9980E004AAEB3 /* SrunBar */ = { + isa = PBXNativeTarget; + buildConfigurationList = A2DEB16D1BC9980E004AAEB3 /* Build configuration list for PBXNativeTarget "SrunBar" */; + buildPhases = ( + A2DEB1461BC9980E004AAEB3 /* Sources */, + A2DEB1471BC9980E004AAEB3 /* Frameworks */, + A2DEB1481BC9980E004AAEB3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SrunBar; + productName = WeatherBar; + productReference = A2DEB14A1BC9980E004AAEB3 /* SrunBar.app */; + productType = "com.apple.product-type.application"; + }; + A2DEB1581BC9980E004AAEB3 /* SrunBarTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A2DEB1701BC9980E004AAEB3 /* Build configuration list for PBXNativeTarget "SrunBarTests" */; + buildPhases = ( + A2DEB1551BC9980E004AAEB3 /* Sources */, + A2DEB1561BC9980E004AAEB3 /* Frameworks */, + A2DEB1571BC9980E004AAEB3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + A2DEB15B1BC9980E004AAEB3 /* PBXTargetDependency */, + ); + name = SrunBarTests; + productName = WeatherBarTests; + productReference = A2DEB1591BC9980E004AAEB3 /* SrunBarTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + A2DEB1631BC9980E004AAEB3 /* SrunBarUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A2DEB1731BC9980E004AAEB3 /* Build configuration list for PBXNativeTarget "SrunBarUITests" */; + buildPhases = ( + A2DEB1601BC9980E004AAEB3 /* Sources */, + A2DEB1611BC9980E004AAEB3 /* Frameworks */, + A2DEB1621BC9980E004AAEB3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + A2DEB1661BC9980E004AAEB3 /* PBXTargetDependency */, + ); + name = SrunBarUITests; + productName = WeatherBarUITests; + productReference = A2DEB1641BC9980E004AAEB3 /* SrunBarUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A2DEB1421BC9980E004AAEB3 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1230; + ORGANIZATIONNAME = Etsy; + TargetAttributes = { + A2DEB1491BC9980E004AAEB3 = { + CreatedOnToolsVersion = 7.0.1; + LastSwiftMigration = 0820; + ProvisioningStyle = Manual; + }; + A2DEB1581BC9980E004AAEB3 = { + CreatedOnToolsVersion = 7.0.1; + LastSwiftMigration = 0820; + TestTargetID = A2DEB1491BC9980E004AAEB3; + }; + A2DEB1631BC9980E004AAEB3 = { + CreatedOnToolsVersion = 7.0.1; + LastSwiftMigration = 0820; + TestTargetID = A2DEB1491BC9980E004AAEB3; + }; + }; + }; + buildConfigurationList = A2DEB1451BC9980E004AAEB3 /* Build configuration list for PBXProject "SrunBar" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A2DEB1411BC9980E004AAEB3; + productRefGroup = A2DEB14B1BC9980E004AAEB3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A2DEB1491BC9980E004AAEB3 /* SrunBar */, + A2DEB1581BC9980E004AAEB3 /* SrunBarTests */, + A2DEB1631BC9980E004AAEB3 /* SrunBarUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A2DEB1481BC9980E004AAEB3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A2DEB1501BC9980E004AAEB3 /* Assets.xcassets in Resources */, + A21862A31BCDEA2E00770E87 /* ConfigWindow.xib in Resources */, + A2DEB1531BC9980E004AAEB3 /* MainMenu.xib in Resources */, + 0E76EEF72597AFC900A24BC8 /* AboutWindow.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2DEB1571BC9980E004AAEB3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2DEB1621BC9980E004AAEB3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A2DEB1461BC9980E004AAEB3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E4165492595114200B48231 /* Hash.swift in Sources */, + 0E4165442593AC4E00B48231 /* SrunAPI.swift in Sources */, + A21862A21BCDEA2E00770E87 /* ConfigWindow.swift in Sources */, + 0E76EEEF2597AEB700A24BC8 /* AboutWindow.swift in Sources */, + A2D582281BCAAF37006A464B /* StatusMenuController.swift in Sources */, + A2DEB14E1BC9980E004AAEB3 /* AppDelegate.swift in Sources */, + 0E76EECF2597175300A24BC8 /* Request.swift in Sources */, + 0E76EEBE2597103900A24BC8 /* model.swift in Sources */, + A2B4247D1BCDE3D300887CB2 /* InfoView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2DEB1551BC9980E004AAEB3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A2DEB15E1BC9980E004AAEB3 /* SrunBarTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2DEB1601BC9980E004AAEB3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A2DEB1691BC9980E004AAEB3 /* SrunBarUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + A2DEB15B1BC9980E004AAEB3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A2DEB1491BC9980E004AAEB3 /* SrunBar */; + targetProxy = A2DEB15A1BC9980E004AAEB3 /* PBXContainerItemProxy */; + }; + A2DEB1661BC9980E004AAEB3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A2DEB1491BC9980E004AAEB3 /* SrunBar */; + targetProxy = A2DEB1651BC9980E004AAEB3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + A2DEB1511BC9980E004AAEB3 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + A2DEB1521BC9980E004AAEB3 /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + A2DEB16B1BC9980E004AAEB3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + A2DEB16C1BC9980E004AAEB3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + }; + name = Release; + }; + A2DEB16E1BC9980E004AAEB3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/SrunBar/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = vouv.SrunBar; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + A2DEB16F1BC9980E004AAEB3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/SrunBar/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = vouv.SrunBar; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + A2DEB1711BC9980E004AAEB3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = WeatherBarTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.etsy.WeatherBarTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SrunBar.app/Contents/MacOS/SrunBar"; + }; + name = Debug; + }; + A2DEB1721BC9980E004AAEB3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = WeatherBarTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.etsy.WeatherBarTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SrunBar.app/Contents/MacOS/SrunBar"; + }; + name = Release; + }; + A2DEB1741BC9980E004AAEB3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = WeatherBarUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.etsy.WeatherBarUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = WeatherBar; + USES_XCTRUNNER = YES; + }; + name = Debug; + }; + A2DEB1751BC9980E004AAEB3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = WeatherBarUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.etsy.WeatherBarUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = WeatherBar; + USES_XCTRUNNER = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A2DEB1451BC9980E004AAEB3 /* Build configuration list for PBXProject "SrunBar" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A2DEB16B1BC9980E004AAEB3 /* Debug */, + A2DEB16C1BC9980E004AAEB3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A2DEB16D1BC9980E004AAEB3 /* Build configuration list for PBXNativeTarget "SrunBar" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A2DEB16E1BC9980E004AAEB3 /* Debug */, + A2DEB16F1BC9980E004AAEB3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A2DEB1701BC9980E004AAEB3 /* Build configuration list for PBXNativeTarget "SrunBarTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A2DEB1711BC9980E004AAEB3 /* Debug */, + A2DEB1721BC9980E004AAEB3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A2DEB1731BC9980E004AAEB3 /* Build configuration list for PBXNativeTarget "SrunBarUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A2DEB1741BC9980E004AAEB3 /* Debug */, + A2DEB1751BC9980E004AAEB3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A2DEB1421BC9980E004AAEB3 /* Project object */; +} diff --git a/SrunBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SrunBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..41dc5b4 --- /dev/null +++ b/SrunBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SrunBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SrunBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/SrunBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SrunBar/AboutWindow.swift b/SrunBar/AboutWindow.swift new file mode 100644 index 0000000..1a7a1a6 --- /dev/null +++ b/SrunBar/AboutWindow.swift @@ -0,0 +1,26 @@ + +import Cocoa + +class AboutWindow: NSWindowController, NSWindowDelegate { + + @IBOutlet weak var versionLabel: NSTextField! + + let version = "v0.2.7" + let link = "https://github.com/vouv/SrunBar" + + override var windowNibName : String! { "AboutWindow" } + + override func windowDidLoad() { + super.windowDidLoad() + self.versionLabel?.stringValue = "SrunBar " + version + self.window?.center() + self.window?.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + } + + @IBAction func linkClicked(_ sender: NSButtonCell) { + let url = URL(string: link) + NSWorkspace.shared.open(url!) + } + +} diff --git a/SrunBar/AboutWindow.xib b/SrunBar/AboutWindow.xib new file mode 100644 index 0000000..a083ccd --- /dev/null +++ b/SrunBar/AboutWindow.xib @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SrunBar/AppDelegate.swift b/SrunBar/AppDelegate.swift new file mode 100644 index 0000000..d4a07e6 --- /dev/null +++ b/SrunBar/AppDelegate.swift @@ -0,0 +1,20 @@ + +import Cocoa + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + + func applicationDidFinishLaunching(_ aNotification: Notification) { + // Insert code here to initialize your application + } + + + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + + +} + diff --git a/SrunBar/Assets.xcassets/AppIcon.appiconset/1024x1024.png b/SrunBar/Assets.xcassets/AppIcon.appiconset/1024x1024.png new file mode 100644 index 0000000..537ab8a Binary files /dev/null and b/SrunBar/Assets.xcassets/AppIcon.appiconset/1024x1024.png differ diff --git a/SrunBar/Assets.xcassets/AppIcon.appiconset/128x128.png b/SrunBar/Assets.xcassets/AppIcon.appiconset/128x128.png new file mode 100644 index 0000000..8bb712b Binary files /dev/null and b/SrunBar/Assets.xcassets/AppIcon.appiconset/128x128.png differ diff --git a/SrunBar/Assets.xcassets/AppIcon.appiconset/16x16.png b/SrunBar/Assets.xcassets/AppIcon.appiconset/16x16.png new file mode 100644 index 0000000..b4a5801 Binary files /dev/null and b/SrunBar/Assets.xcassets/AppIcon.appiconset/16x16.png differ diff --git a/SrunBar/Assets.xcassets/AppIcon.appiconset/256x256-1.png b/SrunBar/Assets.xcassets/AppIcon.appiconset/256x256-1.png new file mode 100644 index 0000000..8f5b20a Binary files /dev/null and b/SrunBar/Assets.xcassets/AppIcon.appiconset/256x256-1.png differ diff --git a/SrunBar/Assets.xcassets/AppIcon.appiconset/256x256.png b/SrunBar/Assets.xcassets/AppIcon.appiconset/256x256.png new file mode 100644 index 0000000..8f5b20a Binary files /dev/null and b/SrunBar/Assets.xcassets/AppIcon.appiconset/256x256.png differ diff --git a/SrunBar/Assets.xcassets/AppIcon.appiconset/32x32-1.png b/SrunBar/Assets.xcassets/AppIcon.appiconset/32x32-1.png new file mode 100644 index 0000000..2cd700f Binary files /dev/null and b/SrunBar/Assets.xcassets/AppIcon.appiconset/32x32-1.png differ diff --git a/SrunBar/Assets.xcassets/AppIcon.appiconset/32x32.png b/SrunBar/Assets.xcassets/AppIcon.appiconset/32x32.png new file mode 100644 index 0000000..2cd700f Binary files /dev/null and b/SrunBar/Assets.xcassets/AppIcon.appiconset/32x32.png differ diff --git a/SrunBar/Assets.xcassets/AppIcon.appiconset/512x512-1.png b/SrunBar/Assets.xcassets/AppIcon.appiconset/512x512-1.png new file mode 100644 index 0000000..e5db368 Binary files /dev/null and b/SrunBar/Assets.xcassets/AppIcon.appiconset/512x512-1.png differ diff --git a/SrunBar/Assets.xcassets/AppIcon.appiconset/512x512.png b/SrunBar/Assets.xcassets/AppIcon.appiconset/512x512.png new file mode 100644 index 0000000..e5db368 Binary files /dev/null and b/SrunBar/Assets.xcassets/AppIcon.appiconset/512x512.png differ diff --git a/SrunBar/Assets.xcassets/AppIcon.appiconset/64x64.png b/SrunBar/Assets.xcassets/AppIcon.appiconset/64x64.png new file mode 100644 index 0000000..533c980 Binary files /dev/null and b/SrunBar/Assets.xcassets/AppIcon.appiconset/64x64.png differ diff --git a/SrunBar/Assets.xcassets/AppIcon.appiconset/Contents.json b/SrunBar/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..5b42da3 --- /dev/null +++ b/SrunBar/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "filename" : "16x16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "32x32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "32x32-1.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "64x64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "128x128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "256x256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "256x256-1.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "512x512.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "512x512-1.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "1024x1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SrunBar/Assets.xcassets/Contents.json b/SrunBar/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SrunBar/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SrunBar/Assets.xcassets/bg.imageset/Contents.json b/SrunBar/Assets.xcassets/bg.imageset/Contents.json new file mode 100644 index 0000000..0dfad71 --- /dev/null +++ b/SrunBar/Assets.xcassets/bg.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "bg-1.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "bg.jpg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "bg-2.jpg", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SrunBar/Assets.xcassets/bg.imageset/bg-1.jpg b/SrunBar/Assets.xcassets/bg.imageset/bg-1.jpg new file mode 100644 index 0000000..d55aa6d Binary files /dev/null and b/SrunBar/Assets.xcassets/bg.imageset/bg-1.jpg differ diff --git a/SrunBar/Assets.xcassets/bg.imageset/bg-2.jpg b/SrunBar/Assets.xcassets/bg.imageset/bg-2.jpg new file mode 100644 index 0000000..d55aa6d Binary files /dev/null and b/SrunBar/Assets.xcassets/bg.imageset/bg-2.jpg differ diff --git a/SrunBar/Assets.xcassets/bg.imageset/bg.jpg b/SrunBar/Assets.xcassets/bg.imageset/bg.jpg new file mode 100644 index 0000000..d55aa6d Binary files /dev/null and b/SrunBar/Assets.xcassets/bg.imageset/bg.jpg differ diff --git a/SrunBar/Assets.xcassets/statusIcon.imageset/18x18.png b/SrunBar/Assets.xcassets/statusIcon.imageset/18x18.png new file mode 100644 index 0000000..a578161 Binary files /dev/null and b/SrunBar/Assets.xcassets/statusIcon.imageset/18x18.png differ diff --git a/SrunBar/Assets.xcassets/statusIcon.imageset/36x36-1.png b/SrunBar/Assets.xcassets/statusIcon.imageset/36x36-1.png new file mode 100644 index 0000000..b0d4406 Binary files /dev/null and b/SrunBar/Assets.xcassets/statusIcon.imageset/36x36-1.png differ diff --git a/SrunBar/Assets.xcassets/statusIcon.imageset/54x54.png b/SrunBar/Assets.xcassets/statusIcon.imageset/54x54.png new file mode 100644 index 0000000..669a4f4 Binary files /dev/null and b/SrunBar/Assets.xcassets/statusIcon.imageset/54x54.png differ diff --git a/SrunBar/Assets.xcassets/statusIcon.imageset/Contents.json b/SrunBar/Assets.xcassets/statusIcon.imageset/Contents.json new file mode 100644 index 0000000..ff8c41a --- /dev/null +++ b/SrunBar/Assets.xcassets/statusIcon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "18x18.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "54x54.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "36x36-1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SrunBar/Base.lproj/MainMenu.xib b/SrunBar/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..ce63342 --- /dev/null +++ b/SrunBar/Base.lproj/MainMenu.xib @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SrunBar/ConfigWindow.swift b/SrunBar/ConfigWindow.swift new file mode 100644 index 0000000..3aa8258 --- /dev/null +++ b/SrunBar/ConfigWindow.swift @@ -0,0 +1,40 @@ + +import Cocoa + +protocol PreferencesWindowDelegate { + func preferencesDidUpdate() +} + +class ConfigWindow: NSWindowController, NSWindowDelegate { +// var delegate: PreferencesWindowDelegate? + + @IBOutlet weak var usernameField: NSTextField! + @IBOutlet weak var passwordField: NSSecureTextField! + + @IBOutlet weak var saveButton: NSButton! + + override var windowNibName : String! { "ConfigWindow" } + + @IBAction func saveClicked(_ sender: NSButton) { + let defaults = UserDefaults.standard + defaults.setValue(usernameField.stringValue, forKey: "username") + defaults.setValue(passwordField.stringValue, forKey: "password") +// delegate?.preferencesDidUpdate() + self.window?.close() + } + + override func windowDidLoad() { + super.windowDidLoad() + self.window?.center() + self.window?.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + + let defaults = UserDefaults.standard + usernameField.stringValue = defaults.string(forKey: "username") ?? "" + passwordField.stringValue = defaults.string(forKey: "password") ?? "" + } + + func windowWillClose(_ notification: Notification) { + + } +} diff --git a/SrunBar/ConfigWindow.xib b/SrunBar/ConfigWindow.xib new file mode 100644 index 0000000..9e35a38 --- /dev/null +++ b/SrunBar/ConfigWindow.xib @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SrunBar/Info.plist b/SrunBar/Info.plist new file mode 100644 index 0000000..fe476b8 --- /dev/null +++ b/SrunBar/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSApplicationCategoryType + public.app-category.utilities + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + LSUIElement + + NSAppTransportSecurity + + NSExceptionDomains + + api.openweathermap.org + + NSExceptionAllowsInsecureHTTPLoads + + + + + NSHumanReadableCopyright + Copyright © 2020 Vouv. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/SrunBar/InfoView.swift b/SrunBar/InfoView.swift new file mode 100644 index 0000000..3f54dab --- /dev/null +++ b/SrunBar/InfoView.swift @@ -0,0 +1,45 @@ + +import Cocoa + +class InfoView: NSView { + @IBOutlet weak var IPField: NSTextField! + @IBOutlet weak var usernameField: NSTextField! + @IBOutlet weak var walletField: NSTextField! + @IBOutlet weak var balanceField: NSTextField! + @IBOutlet weak var volumeField: NSTextField! + @IBOutlet weak var onlineTimeField: NSTextField! + + func update(_ info: RespInfo) { + DispatchQueue.main.async { + // 只能在主线程更新UI + self.IPField.stringValue = info.online_ip + self.usernameField.stringValue = info.user_name + self.walletField.stringValue = String.init(format: "%.2f 元", info.wallet_balance) + self.balanceField.stringValue = String.init(format: "%.2f 元", info.user_balance) + self.volumeField.stringValue = self.parseBytes(info.sum_bytes) + self.onlineTimeField.stringValue = self.parseSeconds(info.sum_seconds) + } + } + + private func parseBytes(_ bytes: Int64) -> String { + let KB: Double = 1024 + let MB = KB * 1024 + let GB = MB * 1024 + let gb = Double(bytes) / GB + if gb > 0 { + return String.init(format: "%.2f GB", gb) + } + let mb = Double(bytes) / MB + return String.init(format: "%.1f MB", mb) + } + + private func parseSeconds(_ seconds: Int64) -> String { + let hour : Int64 = 3600 + let hours = seconds / hour + let minutes = (seconds%3600) / 60 + if hours > 0 { + return String.init(format: "%d小时", hours) + } + return String.init(format: "%d分钟", minutes) + } +} diff --git a/SrunBar/StatusMenuController.swift b/SrunBar/StatusMenuController.swift new file mode 100644 index 0000000..c22c294 --- /dev/null +++ b/SrunBar/StatusMenuController.swift @@ -0,0 +1,155 @@ + +import Cocoa + +class StatusMenuController: NSObject, NSMenuDelegate { + @IBOutlet weak var statusMenu: NSMenu! + @IBOutlet weak var infoView: InfoView! + + var srunMenuItem: NSMenuItem! + + var configWindow: ConfigWindow! + var aboutWindow: AboutWindow! + + @IBOutlet weak var loginItem: NSMenuItem! + + @IBOutlet weak var aboutItem: NSMenuItem! + + var statusIcon: NSImage? { + let icon = NSImage(named: "statusIcon") + // best for dark mode + icon?.isTemplate = true + return icon + } + + let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + let srun = SrunAPI() + + override func awakeFromNib() { + statusItem.menu = statusMenu + // 监听显示事件 + statusMenu.delegate = self + + statusItem.button?.image = statusIcon + + srunMenuItem = statusMenu.item(withTitle: "Info") + srunMenuItem.view = infoView + + // 两个基本页面 + configWindow = ConfigWindow() + aboutWindow = AboutWindow() + + // 注册监听 + NotificationCenter.default.addObserver(forName: NSNotification.Name("login"), object: nil, queue: nil) { (notice) in +// debugPrint("login") + self.doLogin() + } + NotificationCenter.default.addObserver(forName: NSNotification.Name("logout"), object: nil, queue: nil) { (notice) in +// debugPrint("logout") + self.doLogout() + } + NotificationCenter.default.addObserver(forName: NSNotification.Name("info"), object: nil, queue: nil) { (notice) in +// debugPrint("info") + self.doInfo() + } + } + + func doLogin() { + let defaults = UserDefaults.standard + guard let username = defaults.string(forKey: "username") else { + return + } + guard let password = defaults.string(forKey: "password") else { + return + } + srun.login(username: username, password: password) { (res, err) in + if let err = err { + self.notify(title: "登录失败", subtitle: err.localizedDescription) + }else if let res = res { + if res.access_token.count > 0 { + self.notify(title: "登录成功", subtitle: "账号:\(res.username)") + }else if res.error_msg == "Arrearage users" { + self.notify(title: "已欠费", subtitle: "登录失败") + }else { + self.notify(title: "登录异常", subtitle: "\(res.error_msg)") + } + }else { + self.notify(title: "登录失败", subtitle: "未知Bug") + } + } + } + + func doLogout() { + let defaults = UserDefaults.standard + guard let username = defaults.string(forKey: "username") else { + self.notify(title: "错误", subtitle: "请先设置用户名密码") + return + } + + srun.logout(username: username) { (err) in + if let err = err { + self.notify(title: "注销失败", subtitle: err.localizedDescription) + }else { + self.notify(title: "注销成功", subtitle: "账号\(username)") + } + } + } + + func doInfo() { + let defaults = UserDefaults.standard + guard let username = defaults.string(forKey: "username") else { + return + } + guard let password = defaults.string(forKey: "password") else { + return + } + srun.info(username: username, password: password) { (res, error) in + if error != nil { + return + }else if let info = res { + DispatchQueue.main.async { + self.infoView.update(info) + } + } + } + } + + // 打开view自动更新info + func menuWillOpen(_ menu: NSMenu) { + NotificationCenter.default.post(name: NSNotification.Name("info"), object: nil) + } + + + @IBAction func loginClicked(_ sender: NSMenuItem) { + NotificationCenter.default.post(name: NSNotification.Name("login"), object: nil) + } + + @IBAction func logoutClicked(_ sender: NSMenuItem) { + NotificationCenter.default.post(name: NSNotification.Name("logout"), object: nil) + } + + + private func notify(title: String, subtitle: String) { + let nty = NSUserNotification.init() + nty.title = title + nty.subtitle = subtitle +// nty.hasActionButton = false + nty.actionButtonTitle = "关闭" + + NSUserNotificationCenter.default.scheduleNotification(nty) + } + + @IBAction func configClicked(_ sender: NSMenuItem) { + self.configWindow.showWindow(nil) + NSApp.activate(ignoringOtherApps: true) + } + + @IBAction func aboutClicked(_ sender: NSMenuItem) { + self.aboutWindow.showWindow(nil) + NSApp.activate(ignoringOtherApps: true) + } + + @IBAction func quitClicked(_ sender: NSMenuItem) { + NSApplication.shared.terminate(self) + } + +} diff --git a/SrunBar/Store.swift b/SrunBar/Store.swift new file mode 100644 index 0000000..f053ebb --- /dev/null +++ b/SrunBar/Store.swift @@ -0,0 +1,41 @@ +// +// Store.swift +// WeatherBar +// +// Created by vouv on 2020/12/23. +// Copyright © 2020 Etsy. All rights reserved. +// + +import Cocoa + +struct Account { + var username : String + var password : String + var access_token : String + var acid : Int +} + +class Store { + let accountFile = "account.json" + + func getAccount() { + + } + + func setAccount() { + + } + + func initAccount() { + let path = getPath() + let data: NSData? = NSData.init(contentsOfFile: path) + +// NSLog(data?.description) + + } + + private func getPath() -> String { + return NSHomeDirectory() + "/.srun/" + accountFile + } + +} diff --git a/SrunBar/WeatherAPI.swift b/SrunBar/WeatherAPI.swift new file mode 100644 index 0000000..b4f91f2 --- /dev/null +++ b/SrunBar/WeatherAPI.swift @@ -0,0 +1,70 @@ +import Cocoa + +struct Weather: CustomStringConvertible { + var city: String + var currentTemp: Float + var conditions: String + var icon: String + + var description: String { + return "\(city): \(currentTemp)F and \(conditions)" + } +} + +class WeatherAPI { + let API_KEY = "your-api-key-here" + let BASE_URL = "http://api.openweathermap.org/data/2.5/weather" + + func fetchWeather(_ query: String, success: @escaping (Weather) -> Void) { + let session = URLSession.shared + // url-escape the query string we're passed + let escapedQuery = query.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) + let url = URL(string: "\(BASE_URL)?APPID=\(API_KEY)&units=imperial&q=\(escapedQuery!)") + let task = session.dataTask(with: url!) { data, response, err in + // first check for a hard error + if let error = err { + NSLog("weather api error: \(error)") + } + + // then check the response code + if let httpResponse = response as? HTTPURLResponse { + switch httpResponse.statusCode { + case 200: // all good! + if let weather = self.weatherFromJSONData(data!) { + success(weather) + } + case 401: // unauthorized + NSLog("weather api returned an 'unauthorized' response. Did you set your API key?") + default: + NSLog("weather api returned response: %d %@", httpResponse.statusCode, HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode)) + } + } + } + task.resume() + } + + func weatherFromJSONData(_ data: Data) -> Weather? { + typealias JSONDict = [String:AnyObject] + let json : JSONDict + + do { + json = try JSONSerialization.jsonObject(with: data, options: []) as! JSONDict + } catch { + NSLog("JSON parsing failed: \(error)") + return nil + } + + let mainDict = json["main"] as! JSONDict + let weatherList = json["weather"] as! [JSONDict] + let weatherDict = weatherList[0] + + let weather = Weather( + city: json["name"] as! String, + currentTemp: mainDict["temp"] as! Float, + conditions: weatherDict["main"] as! String, + icon: weatherDict["icon"] as! String + ) + + return weather + } +} diff --git a/SrunBar/srun/Hash.swift b/SrunBar/srun/Hash.swift new file mode 100644 index 0000000..cb7c16b --- /dev/null +++ b/SrunBar/srun/Hash.swift @@ -0,0 +1,181 @@ + +import Cocoa +import CryptoKit + +class Hash { + private func charCodeAt(_ str: String, _ index:Int) -> Int { + let cs = str.utf8 + if index >= cs.count { + return 0 + } + let cIndex = cs.index(cs.startIndex, offsetBy: index) + return Int(cs[cIndex]) + } + + private func code2char(_ code: Int) -> Character { + return Character.init(Unicode.Scalar(code)!) + } + + private func s(_ a: String, _ b: Bool) -> [Int] { + let c = a.count + var v = [Int]() + for i in stride(from: 0, to: c, by: 4) { + let tmp = charCodeAt(a, i) | + (charCodeAt(a, i+1) << 8) | + (charCodeAt(a, i+2) << 16) | + (charCodeAt(a, i+3) << 24) + v.append(tmp) + } + if b { + v.append(c) + } + return v + } + + private func l(_ a: [Int], _ b: Bool) -> String { + let d = a.count + var c = (d - 1) << 2 + if b { + let m = a[d-1] + if m < c-3 || m > c { + return "" + } + c = m + } + var res = [String]() + + for s in a { + let item = "\(code2char(s&0xff))\(code2char((s>>8)&0xff))\(code2char((s>>16)&0xff))\(code2char((s>>24)&0xff))" + res.append(item) + } + + if b { + return res[0.. String { + if msg == "" { + return "" + } + var v = s(msg, true) + let k = s(key, false) + let n = v.count - 1 + var z = Int(v[n]) + var y = v[0] + let c = 0x86014019 | 0x183639A0 + var m = 0 + var e = 0 + + var d = 0 + let q = 6 + 52/(n+1) + + for _ in stride(from: q, to: 0, by: -1) { + d = (d + c) & (0x8CE0D9BF | 0x731F2640) + e = d >> 2 & 3 + for p in 0..>5 ^ y<<2 + m += (y>>3 ^ z<<4) ^ (d ^ y) + m += k[(p&3)^e] ^ z + v[p] = (v[p] + m) & (0xEFB8D130 | 0x10472ECF) + z = v[p] + } + y = v[0] + m = z>>5 ^ y<<2 + m += (y>>3 ^ z<<4) ^ (d ^ y) + m += k[(n&3)^e] ^ z + v[n] = (v[n] + m) & (0xBB390742 | 0x44C6F8BD) + z = v[n] + } + return l(v, false) + } + + func pwdHmd5(_ password: String, _ token: String) -> String { + + let dataToken = token.data(using: String.Encoding.utf8)! + let dataPassword = password.data(using: String.Encoding.utf8)! + + // for macOS 10.15+ + var hm = HMAC.init(key: SymmetricKey.init(data: dataToken)) + + hm.update(data: dataPassword) + + let hmd5 = hm.finalize().description + + let startIndex = hmd5.index(hmd5.startIndex, offsetBy: "HMAC with MD5: ".count) + + return "{MD5}\(hmd5[startIndex.. String { + let username = data["username"] ?? "" + let password = data["password"] ?? "" + let acid = data["ac_id"] ?? "" + let ip = data["ip"] ?? "" + let info = data["info"] ?? "" + + let strList: [String] = [ + "", + username, + String(password[password.index(password.startIndex, offsetBy: 5).. String { + let dictKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" + let dictVal = "LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA=" + var dict = [String:String]() + + for (idx, c) in dictKey.enumerated() { + dict["\(c)"] = "\(dictVal[dictVal.index(dictVal.startIndex, offsetBy: idx)])" + } + + let xEncodeJson: [String:String] = [ + "username": data["username"] ?? "", + "password": data["password"] ?? "", + "ip": data["ip"] ?? "", + "acid": data["ac_id"] ?? "", + "enc_ver": "srun_bx1", + ] + + let xEncodeRaw = try! JSONSerialization.data(withJSONObject: xEncodeJson, options: JSONSerialization.WritingOptions.sortedKeys) + + let xen = String(data:xEncodeRaw,encoding:.utf8)! + + let xEncodeRes = xEncode(xen, token) + + // 这里不用utf8字节数组算 + let b64Arr = xEncodeRes.unicodeScalars.map { UInt8($0.value) } + + let b64Res = Data(b64Arr).base64EncodedString() + + var target = "" + for s in b64Res { + target += dict["\(s)"] ?? "" + } + + return "{SRBX1}" + target + } + +} diff --git a/SrunBar/srun/Request.swift b/SrunBar/srun/Request.swift new file mode 100644 index 0000000..93fbd0c --- /dev/null +++ b/SrunBar/srun/Request.swift @@ -0,0 +1,13 @@ + +import Cocoa + +class Request : NSObject, URLSessionTaskDelegate { + + func urlSession(_ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void) { + debugPrint(response) + } +} diff --git a/SrunBar/srun/SrunAPI.swift b/SrunBar/srun/SrunAPI.swift new file mode 100644 index 0000000..95593b2 --- /dev/null +++ b/SrunBar/srun/SrunAPI.swift @@ -0,0 +1,270 @@ + +import Cocoa + + +class SrunAPI { + + let BaseAddr = "http://10.0.0.55" + let ChallengeUrl = "/cgi-bin/get_challenge" + let PortalUrl = "/cgi-bin/srun_portal" + let InfoUrl = "/cgi-bin/rad_user_info" + + let hash = Hash() + + func login(username: String, password: String, handle: @escaping (RespLogin?, Error?) -> Void) { + let (acid, err) = getAcid() + if let err = err { + handle(nil, err) + return + } + + let (respChallenge, errc) = self.getChallenge(username: username) + if let err = errc { + handle(nil, err) + return + } + + let ch = respChallenge! + + let token = ch.challenge + + var formLogin = [ + "action": "login", + "username": username, + "password": password, + "ac_id": "\(acid)", + "ip": ch.clientIP, + "info": "", + "chksum": "", + "n": "200", + "type": "1", + ] + + formLogin["info"] = hash.genInfo(formLogin, token) + formLogin["password"] = hash.pwdHmd5("", token) + formLogin["chksum"] = hash.checksum(formLogin, token) + + self.doRequest(dst: BaseAddr+PortalUrl, params: formLogin, handle: { (res, err) in + if let err = err { + handle(nil, err) + }else if let dict = res { + + let resp = RespLogin( + checkout_date: dict["checkout_date"] as? String ?? "", + online_ip: dict["online_ip"] as? String ?? "", + srun_ver: dict["srun_ver"] as? String ?? "", + ServicesIntfServerIP: dict["ServicesIntfServerIP"] as? String ?? "", + ecode: dict["ecode"] as? String ?? "", + ServerFlag: dict["ServerFlag"] as? String ?? "", + client_ip: dict["client_ip"] as? String ?? "", + ServicesIntfServerPort: dict["ServicesIntfServerPort"] as? String ?? "", + res: dict["res"] as? String ?? "", + sysver: dict["sysver"] as? String ?? "", + username: dict["username"] as? String ?? "", + suc_msg: dict["suc_msg"] as? String ?? "", + access_token: dict["access_token"] as? String ?? "", + error: dict["error"] as? String ?? "", + remain_times: dict["remain_times"] as? String ?? "", + wallet_balance: dict["wallet_balance"] as? Float ?? 0, + error_msg: dict["error_msg"] as? String ?? "", + real_name: dict["real_name"] as? String ?? "", + remain_flux: dict["remain_flux"] as? String ?? "") + + let defaults = UserDefaults.standard + defaults.setValue(resp.access_token, forKey: "access_token") + defaults.setValue(resp.client_ip, forKey: "client_ip") + handle(resp, nil) + } + }) + } + + func logout(username: String, handle: @escaping (Error?) -> Void) { + let params = [ + "action": "logout", + "username": username, + ] + self.doRequest(dst: BaseAddr + PortalUrl, params: params) { (_, error) in + handle(error) + } + } + + func info(username: String, password: String, handle: @escaping (RespInfo?, Error?) -> Void) { + self.doRequest(dst: BaseAddr+InfoUrl, params: nil) { (json, err) in + if let err = err { + handle(nil, err) + }else if let json = json { + handle(RespInfo( + ServerFlag: json["ServerFlag"] as? Int64 ?? 0, + add_time: json["add_time"] as? Int64 ?? 0, + all_bytes: json["all_bytes"]as? Int64 ?? 0, + bytes_in: json["bytes_in"] as? Int64 ?? 0, + bytes_out: json["bytes_out"] as? Int64 ?? 0, + checkout_date: json["checkout_date"] as? Int64 ?? 0, + domain: json["domain"] as? String ?? "", + error: json["error"] as? String ?? "", + group_id: json["group_id"]as? String ?? "", + keepalive_time: json["keepalive_time"]as? Int64 ?? 0, + online_ip: json["online_ip"]as? String ?? "", + products_name: json["products_name"]as? String ?? "", + real_name: json["real_name"]as? String ?? "", + remain_bytes: json["remain_bytes"]as? Int64 ?? 0, + remain_seconds: json["remain_seconds"]as? Int64 ?? 0, + sum_bytes: json["sum_bytes"]as? Int64 ?? 0, + sum_seconds: json["sum_seconds"]as? Int64 ?? 0, + user_balance: json["user_balance"]as? Float64 ?? 0, + user_charge: json["user_charge"]as? Float64 ?? 0, + user_mac: json["user_mac"] as? String ?? "", + user_name: json["user_name"] as? String ?? "", + wallet_balance: json["wallet_balance"] as? Float64 ?? 0), err) + } + } + } + + func getChallenge(username : String) -> (RespChallenge?, Error?) { + var resp : RespChallenge? + var err : Error? + let semaphore = DispatchSemaphore.init(value: 0) + + self.doRequest(dst: BaseAddr + ChallengeUrl, + params: ["username": username, "ip":""]) { (json, error) in + if let error = error { + err = error + }else if let json = json { + resp = RespChallenge( + challenge: json["challenge"] as! String, + clientIP: json["client_ip"] as! String, + onlineIP: json["online_ip"] as! String, + res: json["res"] as! String, + ecode: json["ecode"] as! Int, + error: json["error"] as! String, + errorMsg: json["error_msg"] as! String, + srunVersion: json["srun_ver"] as! String, + expire: json["expire"] as! String, + st: json["st"] as! Int) + } + semaphore.signal() + } + semaphore.wait() + return (resp, err) + } + + private func getAcid() -> (Int, Error?) { + + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = TimeInterval.init(3) + let session = URLSession(configuration: config) + + let semaphore = DispatchSemaphore.init(value: 0) + var acid = 1 + var err : Error? + let task = session.dataTask( + with: URL(string: BaseAddr)!, + completionHandler: { (data, response, error) in + if let error = error { + err = error + }else if let response = response { + let query = response.url?.query ?? "" + if query.contains("ac_id=") { + let range = query.range(of: "ac_id=")! + let end = query.index(range.upperBound, offsetBy: 1) + acid = Int(query[range.upperBound.. Void) { + var addr = URLComponents(string: dst)! + // param + var query = [URLQueryItem]() + + let queryCallback = self.genCallback(); + + query.append(URLQueryItem(name: "callback", value: queryCallback)) + query.append(URLQueryItem(name: "_", value: self.genMillStamp())) + + if let params = params { + for (key,value) in params { + query.append(URLQueryItem(name: key, value: value)) + } + } + + query = query.filter{!$0.name.isEmpty} + + addr.queryItems = query + + // encode + 和 / + addr.percentEncodedQuery = addr.percentEncodedQuery? + .replacingOccurrences(of: "+", with:"%2B") + .replacingOccurrences(of: "/", with:"%2F") + + var urlRequest = URLRequest(url: addr.url!) + urlRequest.httpMethod = "GET" + + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = TimeInterval.init(3) + let session = URLSession(configuration: config) + let task = session.dataTask( + with: urlRequest, + completionHandler: { (data, response, error) in + + if let error = error { + handle(nil, error) + return + } + + let body = String(data: data!, encoding: String.Encoding.utf8)! + + + let startIndex = body.index(body.startIndex, offsetBy: queryCallback.count+1) + let endIndex = body.index(body.endIndex, offsetBy: -1) + + let jsonStr = body[startIndex.. String { + return "jsonp\(Date().milliStamp)" + } + func genMillStamp() -> String { + return "\(Date().milliStamp)" + } +} + +extension Date { + + /// 获取当前 秒级 时间戳 - 10位 + var timeStamp : String { + let timeInterval: TimeInterval = self.timeIntervalSince1970 + let timeStamp = Int(timeInterval) + return "\(timeStamp)" + } + + /// 获取当前 毫秒级 时间戳 - 13位 + var milliStamp : String { + let timeInterval: TimeInterval = self.timeIntervalSince1970 + let millisecond = CLongLong(round(timeInterval*1000)) + return "\(millisecond)" + } +} + diff --git a/SrunBar/srun/model.swift b/SrunBar/srun/model.swift new file mode 100644 index 0000000..1cbc7e1 --- /dev/null +++ b/SrunBar/srun/model.swift @@ -0,0 +1,67 @@ + +import Cocoa + +struct RespChallenge { + let challenge : String + + let clientIP : String + let onlineIP : String + + let res : String + let ecode : Int + let error : String + let errorMsg : String + + + let srunVersion : String + let expire : String + let st : Int +} + +struct RespLogin { + let checkout_date : String + let online_ip : String + let srun_ver : String + let ServicesIntfServerIP : String + let ecode : String + let ServerFlag : String + let client_ip : String + let ServicesIntfServerPort : String + let res : String + let sysver : String + let username : String + let suc_msg : String + let access_token : String + let error : String + let remain_times : String + let wallet_balance : Float + let error_msg : String + let real_name : String + let remain_flux : String +} + + +struct RespInfo { + let ServerFlag : Int64 + let add_time : Int64 + let all_bytes : Int64 + let bytes_in : Int64 + let bytes_out : Int64 + let checkout_date : Int64 + let domain : String + let error : String + let group_id : String + let keepalive_time : Int64 + let online_ip : String + let products_name : String + let real_name : String + let remain_bytes : Int64 + let remain_seconds : Int64 + let sum_bytes : Int64 + let sum_seconds : Int64 + let user_balance : Float64 + let user_charge : Float64 + let user_mac : String + let user_name : String + let wallet_balance : Float64 +} diff --git a/SrunBarTests/Info.plist b/SrunBarTests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/SrunBarTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/SrunBarTests/SrunBarTests.swift b/SrunBarTests/SrunBarTests.swift new file mode 100644 index 0000000..5ec847b --- /dev/null +++ b/SrunBarTests/SrunBarTests.swift @@ -0,0 +1,29 @@ + +import XCTest +@testable import SrunBar + +class SrunBarTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/SrunBarUITests/Info.plist b/SrunBarUITests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/SrunBarUITests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/SrunBarUITests/SrunBarUITests.swift b/SrunBarUITests/SrunBarUITests.swift new file mode 100644 index 0000000..3af178c --- /dev/null +++ b/SrunBarUITests/SrunBarUITests.swift @@ -0,0 +1,29 @@ + +import XCTest + +class SrunBarUITests: XCTestCase { + + override func setUp() { + super.setUp() + + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + XCUIApplication().launch() + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + +} diff --git a/doc/demo.jpg b/doc/demo.jpg new file mode 100644 index 0000000..85285a7 Binary files /dev/null and b/doc/demo.jpg differ