Merge branch 'master' into master

This commit is contained in:
Feng 2023-08-23 11:49:14 +08:00 committed by GitHub
commit ffd205d3e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 3253 additions and 518 deletions

View File

@ -14,7 +14,7 @@ on:
jobs:
deploy:
runs-on: macos-11
runs-on: macos-latest
steps:
- name: Checkout
@ -23,7 +23,7 @@ jobs:
- name: Select Xcode Version
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '13.2'
xcode-version: 'latest'
- name: Setup ruby
uses: ruby/setup-ruby@v1
@ -52,4 +52,4 @@ jobs:
APP_STORE_CONNECT_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_KEY_CONTENT }}
BARK_KEY: ${{ secrets.BARK_KEY }}
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}

View File

@ -10,7 +10,7 @@ on:
jobs:
tests:
runs-on: macos-11
runs-on: macos-12
steps:
- name: Checkout
@ -19,7 +19,7 @@ jobs:
- name: Select Xcode Version
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '13.2'
xcode-version: '14.0.1'
- name: Setup ruby
uses: ruby/setup-ruby@v1

View File

@ -14,8 +14,14 @@
060481EE250F404500BC9799 /* SoundsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060481ED250F404500BC9799 /* SoundsViewController.swift */; };
060481F0250F51CA00BC9799 /* SoundCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060481EF250F51CA00BC9799 /* SoundCell.swift */; };
0604F7DF20620D4900B32F09 /* ServerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0604F7DE20620D4900B32F09 /* ServerManager.swift */; };
0608F06E2994D115006B8029 /* BKDropDownCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0608F06D2994D115006B8029 /* BKDropDownCell.swift */; };
0608F0722994D269006B8029 /* BKDropDownCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0608F0712994D269006B8029 /* BKDropDownCell.xib */; };
06172FDA27F6DAEF002333A4 /* ServerListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06172FD927F6DAEF002333A4 /* ServerListTableViewCell.swift */; };
06172FDC27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06172FDB27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift */; };
061894C529962EB900E001C2 /* GradientButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061894C429962EB900E001C2 /* GradientButton.swift */; };
061894C729A75BEA00E001C2 /* Algorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061894C629A75BEA00E001C2 /* Algorithm.swift */; };
0627DABB298B6EA2002F3F69 /* DropBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0627DABA298B6EA2002F3F69 /* DropBoxView.swift */; };
0627DABD2990D615002F3F69 /* BorderTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0627DABC2990D615002F3F69 /* BorderTextField.swift */; };
062B98C3251B2762004562E7 /* BKButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 062B98C2251B2762004562E7 /* BKButton.swift */; };
062B98C8251B27AE004562E7 /* UINavigationItem+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 062B98C7251B27AE004562E7 /* UINavigationItem+Extension.swift */; };
0632050F250B6DD4001561EC /* gotosleep.caf in Resources */ = {isa = PBXBuildFile; fileRef = 063204F0250B6DD1001561EC /* gotosleep.caf */; };
@ -70,15 +76,15 @@
0642B55C27EB149900453D91 /* MutableTextCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0642B55B27EB149900453D91 /* MutableTextCellViewModel.swift */; };
064CAB9E256BE9090018155C /* PreviewCardCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064CAB9D256BE9090018155C /* PreviewCardCellViewModel.swift */; };
064CABA6256BE9510018155C /* PreviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064CABA5256BE9510018155C /* PreviewModel.swift */; };
0653677629B719BC0038BDB8 /* CryptoSettingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EA329B098DD006AB9CA /* CryptoSettingManager.swift */; };
0653677829B727A60038BDB8 /* CryptoSettingRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0653677729B727A60038BDB8 /* CryptoSettingRelay.swift */; };
065A4D4220EE1A31002EB2DB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 063C499720E36BF9001BCA35 /* Localizable.strings */; };
065AE76B2987777F00323230 /* ArchiveSettingRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065AE76A2987777F00323230 /* ArchiveSettingRelay.swift */; };
065BE4402563D649002A8CA4 /* SoundsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065BE43F2563D649002A8CA4 /* SoundsViewModel.swift */; };
065BE4462563D7E5002A8CA4 /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065BE4452563D7E5002A8CA4 /* ViewModelType.swift */; };
065BE44B2563D8E1002A8CA4 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065BE44A2563D8E1002A8CA4 /* Reusable.swift */; };
065BE4502563D939002A8CA4 /* SoundCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065BE44F2563D939002A8CA4 /* SoundCellViewModel.swift */; };
065BE4552565055F002A8CA4 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 065BE4542565055F002A8CA4 /* HomeViewModel.swift */; };
0660480B2700550600938904 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0660480A2700550600938904 /* Intents.framework */; };
0660480E2700550600938904 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0660480D2700550600938904 /* IntentHandler.swift */; };
066048122700550600938904 /* Intent.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 066048092700550600938904 /* Intent.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
0661A543204FDA4100965E4E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0661A542204FDA4100965E4E /* AppDelegate.swift */; };
0661A545204FDA4100965E4E /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0661A544204FDA4100965E4E /* HomeViewController.swift */; };
0661A54A204FDA4100965E4E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0661A549204FDA4100965E4E /* Assets.xcassets */; };
@ -86,6 +92,7 @@
0667D192247D162C005DE2ED /* MessageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0667D191247D162C005DE2ED /* MessageTableViewCell.swift */; };
0667D194247D1BA0005DE2ED /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0667D193247D1BA0005DE2ED /* Date+Extension.swift */; };
0672CB06256903F700570C9D /* MessageListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0672CB05256903F700570C9D /* MessageListViewModel.swift */; };
06787C392A710568008ABDD7 /* GesturePassTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06787C382A710568008ABDD7 /* GesturePassTextView.swift */; };
067B2EB525693E38008B6BE1 /* MessageTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 067B2EB425693E38008B6BE1 /* MessageTableViewCellViewModel.swift */; };
06802E5320ECC40C00767047 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0661A549204FDA4100965E4E /* Assets.xcassets */; };
06840DBB272298FB001B3193 /* BKColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06840DBA272298FB001B3193 /* BKColor.swift */; };
@ -108,6 +115,7 @@
06BBB8C12567B3EF0076F63E /* BaseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BBB8C02567B3EF0076F63E /* BaseTableViewCell.swift */; };
06BBB8C92567B6730076F63E /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BBB8C82567B6730076F63E /* Operators.swift */; };
06BBB8CE2567B8E60076F63E /* silence.caf in Resources */ = {isa = PBXBuildFile; fileRef = 06BBB8CD2567B8E60076F63E /* silence.caf */; };
06BD4DAA2901352E003364DB /* Object+Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BD4DA92901352E003364DB /* Object+Dictionary.swift */; };
06C2CF232685B88D0034B127 /* TextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C2CF222685B88D0034B127 /* TextCell.swift */; };
06C2CF252685BDB80034B127 /* SpacerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C2CF242685BDB80034B127 /* SpacerCell.swift */; };
06C5952D2480E3F8006B98F3 /* LabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C5952C2480E3F8006B98F3 /* LabelCell.swift */; };
@ -117,6 +125,16 @@
06CF784721C7A50300A052D7 /* NotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 06CF784021C7A50300A052D7 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
06CF784C21C7A51200A052D7 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CF784B21C7A51200A052D7 /* NotificationService.swift */; };
06EE1FD326843E9300586708 /* BarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EE1FD226843E9300586708 /* BarkTests.swift */; };
06EEF333291CCFF400CA228A /* CryptoSettingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EEF332291CCFF400CA228A /* CryptoSettingController.swift */; };
06EEF335291CD00000CA228A /* CryptoSettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EEF334291CD00000CA228A /* CryptoSettingViewModel.swift */; };
06F08EA429B098DD006AB9CA /* CryptoSettingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EA329B098DD006AB9CA /* CryptoSettingManager.swift */; };
06F08EA529B1DDA7006AB9CA /* Algorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061894C629A75BEA00E001C2 /* Algorithm.swift */; };
06F08EA729B1DDFE006AB9CA /* Error+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */; };
06F08EA829B1DE0A006AB9CA /* Error+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */; };
06F08EAA29B1DE9F006AB9CA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 063C499720E36BF9001BCA35 /* Localizable.strings */; };
06F08EAC29B1DECD006AB9CA /* NSLocalizedString+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */; };
06F08EAD29B1DED6006AB9CA /* NSLocalizedString+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */; };
06F08EAF29B5D9FF006AB9CA /* HUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F08EAE29B5D9FF006AB9CA /* HUD.swift */; };
06F11E7727D9D5FB00F00298 /* QRScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F11E7627D9D5FB00F00298 /* QRScannerViewController.swift */; };
3428272069AFAFE2C683FEB0 /* libPods-Bark.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CCC722470308049D180876C7 /* libPods-Bark.a */; };
879AE4D4178855A9672009E4 /* libPods-NotificationServiceExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B7F8BDFAA047451561798F58 /* libPods-NotificationServiceExtension.a */; };
@ -131,13 +149,6 @@
remoteGlobalIDString = 0632CE1D20EC9098003FDF46;
remoteInfo = NotificationContentExtension;
};
066048102700550600938904 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0661A537204FDA4100965E4E /* Project object */;
proxyType = 1;
remoteGlobalIDString = 066048082700550600938904;
remoteInfo = Intent;
};
06CF784521C7A50300A052D7 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0661A537204FDA4100965E4E /* Project object */;
@ -162,7 +173,6 @@
dstSubfolderSpec = 13;
files = (
0632CE2A20EC9098003FDF46 /* NotificationContentExtension.appex in Embed App Extensions */,
066048122700550600938904 /* Intent.appex in Embed App Extensions */,
06CF784721C7A50300A052D7 /* NotificationServiceExtension.appex in Embed App Extensions */,
);
name = "Embed App Extensions";
@ -178,8 +188,14 @@
060481ED250F404500BC9799 /* SoundsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundsViewController.swift; sourceTree = "<group>"; };
060481EF250F51CA00BC9799 /* SoundCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundCell.swift; sourceTree = "<group>"; };
0604F7DE20620D4900B32F09 /* ServerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerManager.swift; sourceTree = "<group>"; };
0608F06D2994D115006B8029 /* BKDropDownCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BKDropDownCell.swift; sourceTree = "<group>"; };
0608F0712994D269006B8029 /* BKDropDownCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BKDropDownCell.xib; sourceTree = "<group>"; };
06172FD927F6DAEF002333A4 /* ServerListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListTableViewCell.swift; sourceTree = "<group>"; };
06172FDB27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListTableViewCellViewModel.swift; sourceTree = "<group>"; };
061894C429962EB900E001C2 /* GradientButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButton.swift; sourceTree = "<group>"; };
061894C629A75BEA00E001C2 /* Algorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Algorithm.swift; sourceTree = "<group>"; };
0627DABA298B6EA2002F3F69 /* DropBoxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropBoxView.swift; sourceTree = "<group>"; };
0627DABC2990D615002F3F69 /* BorderTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BorderTextField.swift; sourceTree = "<group>"; };
062B98C2251B2762004562E7 /* BKButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BKButton.swift; sourceTree = "<group>"; };
062B98C7251B27AE004562E7 /* UINavigationItem+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationItem+Extension.swift"; sourceTree = "<group>"; };
063204F0250B6DD1001561EC /* gotosleep.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = gotosleep.caf; sourceTree = "<group>"; };
@ -234,15 +250,14 @@
0642B55B27EB149900453D91 /* MutableTextCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableTextCellViewModel.swift; sourceTree = "<group>"; };
064CAB9D256BE9090018155C /* PreviewCardCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewCardCellViewModel.swift; sourceTree = "<group>"; };
064CABA5256BE9510018155C /* PreviewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewModel.swift; sourceTree = "<group>"; };
0653677729B727A60038BDB8 /* CryptoSettingRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSettingRelay.swift; sourceTree = "<group>"; };
065AE76A2987777F00323230 /* ArchiveSettingRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveSettingRelay.swift; sourceTree = "<group>"; };
065BE43F2563D649002A8CA4 /* SoundsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundsViewModel.swift; sourceTree = "<group>"; };
065BE4452563D7E5002A8CA4 /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = "<group>"; };
065BE44A2563D8E1002A8CA4 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = "<group>"; };
065BE44F2563D939002A8CA4 /* SoundCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundCellViewModel.swift; sourceTree = "<group>"; };
065BE4542565055F002A8CA4 /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = "<group>"; };
066048092700550600938904 /* Intent.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Intent.appex; sourceTree = BUILT_PRODUCTS_DIR; };
0660480A2700550600938904 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; };
0660480D2700550600938904 /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = "<group>"; };
0660480F2700550600938904 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0661A53F204FDA4100965E4E /* Bark.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bark.app; sourceTree = BUILT_PRODUCTS_DIR; };
0661A542204FDA4100965E4E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
0661A544204FDA4100965E4E /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = "<group>"; };
@ -252,6 +267,7 @@
0667D191247D162C005DE2ED /* MessageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTableViewCell.swift; sourceTree = "<group>"; };
0667D193247D1BA0005DE2ED /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = "<group>"; };
0672CB05256903F700570C9D /* MessageListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListViewModel.swift; sourceTree = "<group>"; };
06787C382A710568008ABDD7 /* GesturePassTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GesturePassTextView.swift; sourceTree = "<group>"; };
067B2EB425693E38008B6BE1 /* MessageTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTableViewCellViewModel.swift; sourceTree = "<group>"; };
0683486A2050F1310024B6DA /* Bark.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bark.entitlements; sourceTree = "<group>"; };
0683487020510FB20024B6DA /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };
@ -274,6 +290,7 @@
06BBB8C02567B3EF0076F63E /* BaseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTableViewCell.swift; sourceTree = "<group>"; };
06BBB8C82567B6730076F63E /* Operators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
06BBB8CD2567B8E60076F63E /* silence.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = silence.caf; sourceTree = "<group>"; };
06BD4DA92901352E003364DB /* Object+Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Object+Dictionary.swift"; sourceTree = "<group>"; };
06C2CF222685B88D0034B127 /* TextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextCell.swift; sourceTree = "<group>"; };
06C2CF242685BDB80034B127 /* SpacerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacerCell.swift; sourceTree = "<group>"; };
06C5952C2480E3F8006B98F3 /* LabelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelCell.swift; sourceTree = "<group>"; };
@ -287,6 +304,12 @@
06EE1FD026843E9300586708 /* BarkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BarkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
06EE1FD226843E9300586708 /* BarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarkTests.swift; sourceTree = "<group>"; };
06EE1FD426843E9300586708 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
06EEF332291CCFF400CA228A /* CryptoSettingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSettingController.swift; sourceTree = "<group>"; };
06EEF334291CD00000CA228A /* CryptoSettingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSettingViewModel.swift; sourceTree = "<group>"; };
06F08EA329B098DD006AB9CA /* CryptoSettingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSettingManager.swift; sourceTree = "<group>"; };
06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+Extension.swift"; sourceTree = "<group>"; };
06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLocalizedString+Extension.swift"; sourceTree = "<group>"; };
06F08EAE29B5D9FF006AB9CA /* HUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUD.swift; sourceTree = "<group>"; };
06F11E7627D9D5FB00F00298 /* QRScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScannerViewController.swift; sourceTree = "<group>"; };
121D9B1ED4E8D26F345BC5C0 /* Pods-BarkTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BarkTests.release.xcconfig"; path = "Target Support Files/Pods-BarkTests/Pods-BarkTests.release.xcconfig"; sourceTree = "<group>"; };
138CE8CB688587E893BC5C44 /* Pods-Bark.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Bark.debug.xcconfig"; path = "Target Support Files/Pods-Bark/Pods-Bark.debug.xcconfig"; sourceTree = "<group>"; };
@ -295,6 +318,7 @@
A69B47DA6DB3B168D5770B45 /* Pods-Bark.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Bark.release.xcconfig"; path = "Target Support Files/Pods-Bark/Pods-Bark.release.xcconfig"; sourceTree = "<group>"; };
B7F8BDFAA047451561798F58 /* libPods-NotificationServiceExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NotificationServiceExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; };
CCC722470308049D180876C7 /* libPods-Bark.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Bark.a"; sourceTree = BUILT_PRODUCTS_DIR; };
E3B5CBEB2A94AC26006E25F9 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
F20815A821395CCA155806A4 /* Pods-NotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.debug.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.debug.xcconfig"; sourceTree = "<group>"; };
FB59D77AB30F7AD98BA72C3E /* Pods-BarkTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BarkTests.debug.xcconfig"; path = "Target Support Files/Pods-BarkTests/Pods-BarkTests.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -309,14 +333,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
066048062700550600938904 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0660480B2700550600938904 /* Intents.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
0661A53C204FDA4100965E4E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -366,6 +382,8 @@
06F11E7627D9D5FB00F00298 /* QRScannerViewController.swift */,
068EC15727ED99C900D5D11E /* ServerListViewController.swift */,
068EC15927ED99E700D5D11E /* ServerListViewModel.swift */,
06EEF332291CCFF400CA228A /* CryptoSettingController.swift */,
06EEF334291CD00000CA228A /* CryptoSettingViewModel.swift */,
);
path = Controller;
sourceTree = "<group>";
@ -395,6 +413,13 @@
0642B55B27EB149900453D91 /* MutableTextCellViewModel.swift */,
06172FD927F6DAEF002333A4 /* ServerListTableViewCell.swift */,
06172FDB27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift */,
0627DABA298B6EA2002F3F69 /* DropBoxView.swift */,
0627DABC2990D615002F3F69 /* BorderTextField.swift */,
0608F06D2994D115006B8029 /* BKDropDownCell.swift */,
0608F0712994D269006B8029 /* BKDropDownCell.xib */,
061894C429962EB900E001C2 /* GradientButton.swift */,
06F08EAE29B5D9FF006AB9CA /* HUD.swift */,
06787C382A710568008ABDD7 /* GesturePassTextView.swift */,
);
path = View;
sourceTree = "<group>";
@ -404,6 +429,8 @@
children = (
06B1158E247BB1FB006D91FB /* Message.swift */,
064CABA5256BE9510018155C /* PreviewModel.swift */,
06BD4DA92901352E003364DB /* Object+Dictionary.swift */,
061894C629A75BEA00E001C2 /* Algorithm.swift */,
);
path = Model;
sourceTree = "<group>";
@ -474,6 +501,11 @@
06BBB8C82567B6730076F63E /* Operators.swift */,
0633E809256A091B00ED0680 /* MJRefresh+Rx.swift */,
06840DBA272298FB001B3193 /* BKColor.swift */,
065AE76A2987777F00323230 /* ArchiveSettingRelay.swift */,
06F08EA329B098DD006AB9CA /* CryptoSettingManager.swift */,
0653677729B727A60038BDB8 /* CryptoSettingRelay.swift */,
06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */,
06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */,
);
path = Common;
sourceTree = "<group>";
@ -488,15 +520,6 @@
path = Moya;
sourceTree = "<group>";
};
0660480C2700550600938904 /* Intent */ = {
isa = PBXGroup;
children = (
0660480D2700550600938904 /* IntentHandler.swift */,
0660480F2700550600938904 /* Info.plist */,
);
path = Intent;
sourceTree = "<group>";
};
0661A536204FDA4100965E4E = {
isa = PBXGroup;
children = (
@ -509,7 +532,6 @@
0632CE2120EC9098003FDF46 /* NotificationContentExtension */,
06CF784121C7A50300A052D7 /* NotificationServiceExtension */,
06EE1FD126843E9300586708 /* BarkTests */,
0660480C2700550600938904 /* Intent */,
0661A540204FDA4100965E4E /* Products */,
99BD309BDB7F62B5DC0CECE1 /* Frameworks */,
E9062CB15F1B70C3719578FB /* Pods */,
@ -523,7 +545,6 @@
0632CE1E20EC9098003FDF46 /* NotificationContentExtension.appex */,
06CF784021C7A50300A052D7 /* NotificationServiceExtension.appex */,
06EE1FD026843E9300586708 /* BarkTests.xctest */,
066048092700550600938904 /* Intent.appex */,
);
name = Products;
sourceTree = "<group>";
@ -608,23 +629,6 @@
productReference = 0632CE1E20EC9098003FDF46 /* NotificationContentExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
066048082700550600938904 /* Intent */ = {
isa = PBXNativeTarget;
buildConfigurationList = 066048132700550600938904 /* Build configuration list for PBXNativeTarget "Intent" */;
buildPhases = (
066048052700550600938904 /* Sources */,
066048062700550600938904 /* Frameworks */,
066048072700550600938904 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = Intent;
productName = Intent;
productReference = 066048092700550600938904 /* Intent.appex */;
productType = "com.apple.product-type.app-extension";
};
0661A53E204FDA4100965E4E /* Bark */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0661A551204FDA4100965E4E /* Build configuration list for PBXNativeTarget "Bark" */;
@ -641,7 +645,6 @@
dependencies = (
0632CE2920EC9098003FDF46 /* PBXTargetDependency */,
06CF784621C7A50300A052D7 /* PBXTargetDependency */,
066048112700550600938904 /* PBXTargetDependency */,
);
name = Bark;
productName = Bark;
@ -699,10 +702,6 @@
CreatedOnToolsVersion = 9.3;
ProvisioningStyle = Manual;
};
066048082700550600938904 = {
CreatedOnToolsVersion = 13.0;
ProvisioningStyle = Manual;
};
0661A53E204FDA4100965E4E = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Manual;
@ -731,6 +730,7 @@
en,
Base,
"zh-Hans",
tr,
);
mainGroup = 0661A536204FDA4100965E4E;
productRefGroup = 0661A540204FDA4100965E4E /* Products */;
@ -741,7 +741,6 @@
0632CE1D20EC9098003FDF46 /* NotificationContentExtension */,
06CF783F21C7A50300A052D7 /* NotificationServiceExtension */,
06EE1FCF26843E9300586708 /* BarkTests */,
066048082700550600938904 /* Intent */,
);
};
/* End PBXProject section */
@ -757,13 +756,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
066048072700550600938904 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
0661A53D204FDA4100965E4E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -789,6 +781,7 @@
0661A54D204FDA4100965E4E /* LaunchScreen.storyboard in Resources */,
0632051E250B6DD4001561EC /* choo.caf in Resources */,
06320516250B6DD4001561EC /* calypso.caf in Resources */,
0608F0722994D269006B8029 /* BKDropDownCell.xib in Resources */,
0632052D250B6DD4001561EC /* newsflash.caf in Resources */,
06320514250B6DD4001561EC /* sherwoodforest.caf in Resources */,
0632051A250B6DD4001561EC /* fanfare.caf in Resources */,
@ -810,6 +803,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
06F08EAA29B1DE9F006AB9CA /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -852,6 +846,7 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Bark/Pods-Bark-resources.sh",
"${PODS_ROOT}/DropDown/DropDown/resources/DropDownCell.xib",
"${PODS_ROOT}/MJRefresh/MJRefresh/MJRefresh.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/Material/com.cosmicmind.material.icons.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/Material/com.cosmicmind.material.fonts.bundle",
@ -860,6 +855,7 @@
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/DropDownCell.nib",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MJRefresh.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/com.cosmicmind.material.icons.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/com.cosmicmind.material.fonts.bundle",
@ -926,14 +922,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
066048052700550600938904 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0660480E2700550600938904 /* IntentHandler.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
0661A53B204FDA4100965E4E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -943,21 +931,30 @@
06BBB896256518760076F63E /* NewServerViewModel.swift in Sources */,
06BBB8C92567B6730076F63E /* Operators.swift in Sources */,
0603706920E1F89500F4CA05 /* PreviewCardCell.swift in Sources */,
0627DABB298B6EA2002F3F69 /* DropBoxView.swift in Sources */,
06AE311C266F54A500B39FBB /* GroupTableViewCell.swift in Sources */,
0672CB06256903F700570C9D /* MessageListViewModel.swift in Sources */,
0627DABD2990D615002F3F69 /* BorderTextField.swift in Sources */,
0633E80A256A091B00ED0680 /* MJRefresh+Rx.swift in Sources */,
0637FA8C20E0D7A700E80174 /* BaseViewController.swift in Sources */,
0642B55C27EB149900453D91 /* MutableTextCellViewModel.swift in Sources */,
062B98C8251B27AE004562E7 /* UINavigationItem+Extension.swift in Sources */,
060481EE250F404500BC9799 /* SoundsViewController.swift in Sources */,
06EEF333291CCFF400CA228A /* CryptoSettingController.swift in Sources */,
0603706D20E23EC000F4CA05 /* BarkSFSafariViewController.swift in Sources */,
06AE311A266F4E6600B39FBB /* GroupFilterViewModel.swift in Sources */,
06F08EA429B098DD006AB9CA /* CryptoSettingManager.swift in Sources */,
06BD4DAA2901352E003364DB /* Object+Dictionary.swift in Sources */,
06C5953124811392006B98F3 /* ArchiveSettingCell.swift in Sources */,
068EC15A27ED99E700D5D11E /* ServerListViewModel.swift in Sources */,
06172FDC27F6DB06002333A4 /* ServerListTableViewCellViewModel.swift in Sources */,
061894C729A75BEA00E001C2 /* Algorithm.swift in Sources */,
065BE4502563D939002A8CA4 /* SoundCellViewModel.swift in Sources */,
06F08EA729B1DDFE006AB9CA /* Error+Extension.swift in Sources */,
06B1158F247BB1FB006D91FB /* Message.swift in Sources */,
06172FDA27F6DAEF002333A4 /* ServerListTableViewCell.swift in Sources */,
0653677829B727A60038BDB8 /* CryptoSettingRelay.swift in Sources */,
061894C529962EB900E001C2 /* GradientButton.swift in Sources */,
06C595362481160F006B98F3 /* BKLabel.swift in Sources */,
0637FA7820E0926D00E80174 /* BarkTargetType.swift in Sources */,
064CABA6256BE9510018155C /* PreviewModel.swift in Sources */,
@ -983,12 +980,15 @@
062B98C3251B2762004562E7 /* BKButton.swift in Sources */,
06885EB6247FB9880004A303 /* MessageSettingsViewController.swift in Sources */,
06F11E7727D9D5FB00F00298 /* QRScannerViewController.swift in Sources */,
0608F06E2994D115006B8029 /* BKDropDownCell.swift in Sources */,
06C5952D2480E3F8006B98F3 /* LabelCell.swift in Sources */,
06787C392A710568008ABDD7 /* GesturePassTextView.swift in Sources */,
068EC15827ED99C900D5D11E /* ServerListViewController.swift in Sources */,
0637FA7C20E0930E00E80174 /* BarkApi.swift in Sources */,
06C2CF252685BDB80034B127 /* SpacerCell.swift in Sources */,
06BBB8BC2567B3AD0076F63E /* ArchiveSettingCellViewModel.swift in Sources */,
0642B55A27EB13F100453D91 /* MutableTextCell.swift in Sources */,
06EEF335291CD00000CA228A /* CryptoSettingViewModel.swift in Sources */,
0637FA8020E0981E00E80174 /* BarkSettings.swift in Sources */,
065BE4402563D649002A8CA4 /* SoundsViewModel.swift in Sources */,
065BE4462563D7E5002A8CA4 /* ViewModelType.swift in Sources */,
@ -997,6 +997,9 @@
06BBB8B72567AC140076F63E /* MessageSettingsViewModel.swift in Sources */,
06BBB8C12567B3EF0076F63E /* BaseTableViewCell.swift in Sources */,
0661A543204FDA4100965E4E /* AppDelegate.swift in Sources */,
06F08EAF29B5D9FF006AB9CA /* HUD.swift in Sources */,
065AE76B2987777F00323230 /* ArchiveSettingRelay.swift in Sources */,
06F08EAC29B1DECD006AB9CA /* NSLocalizedString+Extension.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1005,8 +1008,12 @@
buildActionMask = 2147483647;
files = (
06CF784C21C7A51200A052D7 /* NotificationService.swift in Sources */,
06F08EAD29B1DED6006AB9CA /* NSLocalizedString+Extension.swift in Sources */,
0653677629B719BC0038BDB8 /* CryptoSettingManager.swift in Sources */,
06F08EA529B1DDA7006AB9CA /* Algorithm.swift in Sources */,
06BBB89125650CCF0076F63E /* ArchiveSettingManager.swift in Sources */,
06B11591247BC132006D91FB /* Message.swift in Sources */,
06F08EA829B1DE0A006AB9CA /* Error+Extension.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1027,11 +1034,6 @@
target = 0632CE1D20EC9098003FDF46 /* NotificationContentExtension */;
targetProxy = 0632CE2820EC9098003FDF46 /* PBXContainerItemProxy */;
};
066048112700550600938904 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 066048082700550600938904 /* Intent */;
targetProxy = 066048102700550600938904 /* PBXContainerItemProxy */;
};
06CF784621C7A50300A052D7 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 06CF783F21C7A50300A052D7 /* NotificationServiceExtension */;
@ -1058,6 +1060,7 @@
children = (
063C499620E36BF9001BCA35 /* en */,
063C499820E36C15001BCA35 /* zh-Hans */,
E3B5CBEB2A94AC26006E25F9 /* tr */,
);
name = Localizable.strings;
sourceTree = "<group>";
@ -1115,63 +1118,6 @@
};
name = Release;
};
066048142700550600938904 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 5U8LBRXG3A;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Intent/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Intent;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Fin. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = me.fin.bark.Intent;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match Development me.fin.bark.Intent";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
066048152700550600938904 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 5U8LBRXG3A;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Intent/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Intent;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Fin. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = me.fin.bark.Intent;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.fin.bark.Intent";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
0661A54F204FDA4100965E4E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1439,15 +1385,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
066048132700550600938904 /* Build configuration list for PBXNativeTarget "Intent" */ = {
isa = XCConfigurationList;
buildConfigurations = (
066048142700550600938904 /* Debug */,
066048152700550600938904 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0661A53A204FDA4100965E4E /* Build configuration list for PBXProject "Bark" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -8,6 +8,7 @@
import CloudKit
import IceCream
import IQKeyboardManagerSwift
import Material
import RealmSwift
import UIKit
@ -50,6 +51,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// Realm()
setupRealm()
IQKeyboardManager.shared.enable = true
self.window = UIWindow(frame: UIScreen.main.bounds)
let tabBarController = StateStorageTabBarController()
tabBarController.tabBar.tintColor = BKColor.grey.darken4
@ -148,7 +151,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
let alertController = UIAlertController(title: title, message: body, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "复制内容", style: .default, handler: { _ in
alertController.addAction(UIAlertAction(title: NSLocalizedString("CopyContent"), style: .default, handler: { _ in
if let copy = userInfo["copy"] as? String {
UIPasteboard.general.string = copy
}
@ -156,7 +159,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
UIPasteboard.general.string = body
}
}))
alertController.addAction(UIAlertAction(title: "更多操作", style: .default, handler: { _ in
alertController.addAction(UIAlertAction(title: NSLocalizedString("MoreActions"), style: .default, handler: { _ in
var shareContent = ""
if let title = title {
shareContent += "\(title)\n"
@ -180,7 +183,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
applicationActivities: nil)
controller?.present(activityController, animated: true, completion: nil)
}))
alertController.addAction(UIAlertAction(title: "取消", style: .cancel, handler: nil))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
navigationController?.present(alertController, animated: true, completion: nil)
}
@ -207,7 +210,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func applicationWillEnterForeground(_ application: UIApplication) {
ServerManager.shared.syncAllServers()
// -1
// 0
UIApplication.shared.applicationIconBadgeNumber = -1

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "216",
"green" : "46",
"red" : "34"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "233",
"green" : "44",
"red" : "70"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "214",
"green" : "214",
"red" : "214"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "92",
"green" : "92",
"red" : "92"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -2,6 +2,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSUserActivityTypes</key>
<array>
<string>INSendMessageIntent</string>
<string>INStartCallIntent</string>
</array>
<key>NSCameraUsageDescription</key>
<string>We are using the camera to scan a QR code</string>
<key>GitHub Run Id</key>

View File

@ -11,17 +11,21 @@ SecureConnection = "Secure Connection (HTTPS)";
InsecureConnection = "Insecure Connection (HTTP)";
RegisterDevice = "Register Device";
CustomedNotificationContent = "Customed Notification Content";
CustomedNotificationTitle = "Customed Notification Title";
CustomedNotificationContent = "Body Text";
CustomedNotificationTitle = "Title";
Notice1 = "Click on the buttons in the upper right corner \nto copy or preview test URL";
Notice2 = "Customed notification content and title";
Notice1 = "Use the buttons to copy and open sample URLs";
Notice2 = "Customize notification title and body text";
Copy = "Copied!";
Copy2 = "Copy";
CopyAll = "Copy All";
CopyContent = "Copy the Content";
Cancel = "Cancel";
AllowNotifications = "Notifications have been turned off, Please allow notifications and try again";
MoreActions = "More Actions";
AllowNotifications = "Notifications have been turned off. Please allow notifications in the Settings app.";
UnregisteredDevice = "Unregistered Device";
@ -29,45 +33,45 @@ ServerError = "Server Error";
ServerAddress = "Server Address";
ServerExample = "Enter the server address, for example: https://api.day.app";
ServerExample = "Enter the server address, e.g., https://api.day.app";
DeploymentDocuments = "Deployment Documentation";
DeploymentDocuments = "Deploy a self-hosted Bark server";
AddServer = "Add Customed Server";
AddServer = "Add a Server";
AddedSuccessfully = "Added Successfully!";
AddedSuccessfully = "Server has been added successfully.";
InvalidServer = "Invalid server! please try again.";
InvalidServer = "Server address is invalid.";
InvalidURL = "Invalid URL!";
InvalidURL = "URL is invalid.";
urlParameter = "Clicking on the notification will jump \nto this URL in the parameter";
urlParameter = "Open an URL by clicking on a notification, URL schemes are supported.";
copyParameter = "If the URL has a copy parameter then the value of the copy \nparameter will be copied";
copyParameter = "Specify clipboard content by passing through the 'copy' parameter";
automaticallyCopyTitle = "Automatically copy push content (No longer available after iOS14.5)";
automaticallyCopy = "Automatically copy push content \nwhen a push is received\nAfter iOS 14.5, long press or pull down on a push to trigger auto-copy, before iOS 14.5 you don't need to do anything";
automaticallyCopyTitle = "Auto Copy Body Text (iOS 14.4 and below)";
automaticallyCopy = "Automatically copy the body text of a notification, only for iOS 14.4 and below. For iOS 14.5 and above, long press or pull down the notification to copy.";
historyMessage = "Message History";
unknown = "Unknown";
available = "Available";
restricted = "Restricted";
unknown = "Unknown Status";
available = "Enabled";
restricted = "Disabled";
settings = "Settings";
iCloudSatatus = "iCloud sync";
iCloudSync = "Notification messages will be synced when iCloud is available";
iCloudSatatus = "iCloud Sync";
iCloudSync = "Sync notifications through iCloud";
defaultArchiveSettings = "Archive by default";
archiveNote = "When the isArchive parameter is not specified in the push request URL, whether to archive the notification message will be decided according to this setting";
defaultArchiveSettings = "Archive by Default";
archiveNote = "Archive notifications by default if the 'isArchive' parameter is unset.";
archiveNotificationMessageTitle = "Archive notification message";
archiveNotificationMessage = "Notification message will be archived when isArchive value is 1, and will not be archived when the value is 0.\nIf the isArchive parameter is not specified, the notification message will be archived according to the default settings";
archiveNotificationMessageTitle = "Archive Notification";
archiveNotificationMessage = "Archive a notification by passing 'isArchive=1'. Prevent archiving by passing 'isArchive=0'. Default settings will apply if the parameter is unset.";
notificationSound = "Notification sound";
previewSound = "Click to preview";
setSounds = "You can set different sounds for push notifications";
viewAllSounds = "View all sounds";
notificationSound = "Alert Sound";
previewSound = "Click to Play";
setSounds = "Select an alert sound for a notification.";
viewAllSounds = "Click here to view all available sounds.";
service = "Service";
@ -79,43 +83,69 @@ allTime = "All time";
clearFrom = "Clear from:";
clear = "Clear";
group = "Group";
group = "Groups";
done = "Done";
default = "Default";
hideAllGroups = "Unselect All Groups";
showAllGroups = "Select All Groups";
groupMessagesNotice = "Grouping of messages allows you to view messages by group.";
messageGroup = "Message grouping";
groupMessagesNotice = "Let iOS sort notifications by groups.";
messageGroup = "Notification Grouping";
info = "Info";
buildDesc = "Bark was uploaded to the App Store by Github Actions.";
buildDesc = "Bark uses GitHub Actions to build and publish itself to the App Store. You can verify the run_id in the URL to ensure Bark was built from its original open-source codebase.";
other = "Other";
faq = "FAQ";
faq = "FAQs";
appSC = "App Source Code";
backendSC = "Backend Source Code";
notificationIcon = "Custom Icon";
notificationIconNotice = "Set a custom icon for the push and the set icon will replace the default Bark icon. Icons are automatically cached locally.";
notificationIcon = "Icon";
notificationIconNotice = "Display a custom icon on a notification.";
interruptionLevel = "Time Sensitive notifications";
interruptionLevelNotice = "The interruption level can be set for notifications; if not set, the default is active.\n\nAvailable parameter values:\nactive: The system presents the notification immediately, lights up the screen.\ntimeSensitive: May be presented during Do Not Disturb\npassive: Added to the notification list; does not light up screen or play sound.";
interruptionLevel = "Time Sensitive Notifications";
interruptionLevelNotice = "Set importance and delivery timing of a notification. Available values:\nactive (default): presents the notification immediately, lights up the screen, and can play a sound.\ntimeSensitive: similar to active, but can break through system controls such as Notification Summary and Focus.\npassive: adds the notification to the notification list without lighting up the screen or playing a sound.";
badge = "Modify a Notification Badge";
badgeNotice = "The notification badge can be set with a number.";
badge = "Badge Count";
badgeNotice = "Specify a number on Bark apps icon when a notification arrives.";
deviceTokenInfo = "Token for APNs, click to copy to clipboard.";
deviceTokenInfo = "Token for APNs. Click to copy.";
serverList = "Server List";
copyAddressAndKey = "Copy address and key";
copyAddressAndKey = "Copy Address and Key";
resetKey = "Reset or Restore Key";
resetKeyDesc = "Click confirm to reset, enter the old Key to restore";
resetKeyPlaceholder = "Do not enter or enter the old Key";
resetKeyDesc = "Leave blank to reset. Enter an old key to restore.";
resetKeyPlaceholder = "";
confirm = "Confirm";
deleteServer = "Delete server";
confirmDeleteServer = "Sure to delete the server?";
deleteFailed = "Cannot be deleted, the server must keep at least one";
deletedSuccessfully = "Deleted successfully";
resetFailed = "Reset failed";
resetFailed2 = "Failed to reset, did not get DeviceToken";
deleteServer = "Delete Server";
confirmDeleteServer = "Are you sure you want to delete this server?";
deleteFailed = "You must have at least one server configured.";
deletedSuccessfully = "Server has been deleted successfully.";
resetFailed = "Reset Failed";
resetFailed2 = "Bark could not get a DeviceToken from the server.";
export = "Export";
import = "Import";
exportOrImport = "Export and import messages";
items = "messages";
enterKey = "Please enter %d-bit Key";
enterIv = "Please enter 16-bit Iv";
encryptionSettings = "Encryption Settings";
algorithm = "Algorithm";
mode = "Mode";
copyExample = "Copy Send Script Example";
preview = "Preview";
pushNotificationEncryption = "Push Notification Encryption";
encryptionNotice = "Uses a custom key to encrypt and decrypt the push content when sending and receiving. In this way, the push content will not be obtained or leaked by Bark server and Apple APNs server during transmission.";
faqUrl = "https://bark.day.app/#/en-us/faq";
docUrl = "https://bark.day.app/#/en-us/?id=bark";
encryptionUrl = "https://bark.day.app/#/en-us/encryption";
deployUrl = "https://bark.day.app/#/en-us/deploy";
documentation = "Documentation";
ivComment = "IV can be randomly generated, but if it is random, it needs to be passed in the iv parameter.";
opensslEncodingComment = "openssl requires Hex encoding of manual keys and IVs, not ASCII encoding.";
ciphertextComment = "URL encoding the ciphertext, there may be special characters.";
consoleComment = "The console will print";
keyComment = "Must be %d bit long";

View File

@ -0,0 +1,151 @@
/*
Localizable.strings
Bark
Created by furkanipek on 2023/8/22.
Copyright © 2018 Fin. All rights reserved.
*/
SecureConnection = "Güvenli Bağlantı (HTTPS)";
InsecureConnection = "Güvensiz Bağlantı (HTTP)";
RegisterDevice = "Cihazı Kaydet";
CustomedNotificationContent = "Gövde Metni";
CustomedNotificationTitle = "Başlık";
Notice1 = "Örnek URL'leri kopyalamak ve açmak için düğmeleri kullanın";
Notice2 = "Bildirim başlığını ve gövde metnini özelleştirme";
Copy = "Kopyalandı!";
Copy2 = "Kopyala";
CopyAll = "Tümünü Kopyala";
CopyContent = "İçeriği Kopyala";
Cancel = "Vazgeç";
MoreActions = "Daha Fazla İşlem";
AllowNotifications = "Bildirimler kapatıldı. Lütfen Ayarlar uygulamasından bildirimlere izin verin.";
UnregisteredDevice = "Kayıtsız Cihaz";
ServerError = "Sunucu Hatası";
ServerAddress = "Sunucu Adresi";
ServerExample = "Sunucu adresini girin, örneğin, https://api.day.app";
DeploymentDocuments = "Sunucu tarafı dağıtım dökümanını görüntüleyin";
AddServer = "Sunucu Ekleme";
AddedSuccessfully = "Sunucu başarıyla eklendi.";
InvalidServer = "Sunucu adresi geçersiz.";
InvalidURL = "URL geçersiz.";
urlParameter = "Bir bildirime tıklayarak bir URL açın, URL şemaları desteklenir.";
copyParameter = "'copy' parametresini geçirerek pano içeriğini belirtin";
automaticallyCopyTitle = "Gövde Metnini Otomatik Kopyala (iOS 14.4 ve altı)";
automaticallyCopy = "Bir bildirimin gövde metnini otomatik olarak kopyalayın, yalnızca iOS 14.4 ve altı için. iOS 14.5 ve üzeri sürümlerde, kopyalamak için bildirime uzun basın veya aşağı çekin.";
historyMessage = "Mesaj Geçmişi";
unknown = "Bilinmeyen Durum";
available = "Etkin";
restricted = "Devre Dışı";
settings = "Ayarlar";
iCloudSatatus = "iCloud Senkronizasyonu";
iCloudSync = "Bildirimleri iCloud aracılığıyla senkronize edin";
defaultArchiveSettings = "Varsayılan Olarak Arşivle";
archiveNote = "'isArchive' parametresi ayarlanmamışsa varsayılan olarak bildirimleri arşivleyin.";
archiveNotificationMessageTitle = "Arşiv Bildirimi";
archiveNotificationMessage = "'isArchive=1' değerini geçerek bir bildirimi arşivleyin. 'isArchive=0' geçerek arşivlemeyi önleyin. Parametre ayarlanmamışsa varsayılan ayarlar geçerli olacaktır.";
notificationSound = "Uyarı Sesi";
previewSound = "Oynamak için Tıklayın";
setSounds = "Bildirim için bir uyarı sesi seçin.";
viewAllSounds = "Mevcut tüm sesleri görüntülemek için buraya tıklayın.";
service = "Hizmet";
lastHour = "Son bir saat";
today = "Bugün";
todayAndYesterday = "Dün ve Bugün";
allTime = "Tüm zamanlar";
clearFrom = "Şuradan Temizle:";
clear = "Temizle";
group = "Gruplar";
done = "Bitti";
default = "Varsayılan";
hideAllGroups = "Tüm Grupların Seçimini Kaldır";
showAllGroups = "Tüm Grupları Seç";
groupMessagesNotice = "iOS'un bildirimleri gruplara göre sıralamasına izin verin.";
messageGroup = "Bildirim Gruplandırma";
info = "Bilgi";
buildDesc = "Bark, kendisini oluşturmak ve App Store'da yayınlamak için GitHub Actions'ı kullanır. Bark'ın orijinal açık kaynak kod tabanından oluşturulduğundan emin olmak için URL'deki 'run_id'yi doğrulayabilirsiniz.";
other = "Diğer";
faq = "SSS";
appSC = "Uygulama Kaynak Kodu";
backendSC = "Arka Uç Kaynak Kodu";
notificationIcon = "İkon";
notificationIconNotice = "Bildirimde özel bir ikon görüntüleyin.";
interruptionLevel = "Zamana Duyarlı Bildirimler";
interruptionLevelNotice = "Bir bildirimin önemini ve teslim zamanlamasını ayarlayın. Kullanılabilir değerler:\nactive (varsayılan): bildirimi hemen sunar, ekranı aydınlatır ve bir ses çalabilir.\ntimeSensitive: aktif'e benzer, ancak Bildirim Özeti ve Odak gibi sistem denetimlerini aşabilir.\npassive: bildirimi ekranı aydınlatmadan veya bir ses çalmadan bildirim listesine ekler.";
badge = "Rozet Sayısı";
badgeNotice = "Bir bildirim geldiğinde Bark uygulamasının simgesinde bir sayı belirtin.";
deviceTokenInfo = "APNs için token. Kopyalamak için tıklayın.";
serverList = "Sunucu Listesi";
copyAddressAndKey = "Adres ve Anahtarı Kopyala";
resetKey = "Anahtarı Sıfırla veya Geri Yükle";
resetKeyDesc = "Sıfırlamak için boş bırakın. Geri yüklemek için eski bir anahtar girin.";
resetKeyPlaceholder = "";
confirm = "Onayla";
deleteServer = "Sunucuyu Sil";
confirmDeleteServer = "Bu sunucuyu silmek istediğinizden emin misiniz?";
deleteFailed = "Yapılandırılmış en az bir sunucunuz olmalıdır.";
deletedSuccessfully = "Sunucu başarıyla silindi.";
resetFailed = "Sıfırlama Başarısız Oldu";
resetFailed2 = "Bark sunucudan bir DeviceToken alamadı.";
export = "Dışa Aktar";
import = "İçe Aktar";
exportOrImport = "Mesajları dışa ve içe aktarma";
items = "mesajlar";
enterKey = "Lütfen %d-bit Anahtar girin";
enterIv = "Lütfen 16 bit Iv girin";
encryptionSettings = "Şifreleme Ayarları";
algorithm = "Algoritma";
mode = "Mod";
copyExample = "Kopya Gönderme Komut Dosyası Örneği";
preview = "Önizleme";
pushNotificationEncryption = "Anlık Bildirim Şifreleme";
encryptionNotice = "Gönderirken ve alırken push içeriğini şifrelemek ve şifresini çözmek için özel bir anahtar kullanır. Bu sayede push içeriği iletim sırasında Bark sunucusu ve Apple APNs sunucusu tarafından elde edilemez veya sızdırılamaz.";
faqUrl = "https://bark.day.app/#/tr/faq";
docUrl = "https://bark.day.app/#/tr/?id=bark";
encryptionUrl = "https://bark.day.app/#/tr/encryption";
deployUrl = "https://bark.day.app/#/tr/deploy";
documentation = "Dokümantasyon";
ivComment = "IV rastgele oluşturulabilir, ancak rastgele ise iv parametresinde belirtilmesi gerekir.";
opensslEncodingComment = "openssl, manuel anahtarların ve IV'lerin ASCII kodlamasını değil, Hex kodlamasını gerektirir.";
ciphertextComment = "URL şifreli metni kodlarken özel karakterler olabilir.";
consoleComment = "Konsola şunları yazdırır";
keyComment = "%d bit uzunluğunda olmalıdır";

View File

@ -19,8 +19,12 @@ Notice2 = "推送标题的字号比推送内容粗一点";
Copy = "复制成功";
Copy2 = "复制";
CopyAll = "复制全部内容";
CopyContent = "复制内容";
Cancel = "取消";
MoreActions = "更多操作";
AllowNotifications = "绑定设备需要推送。请打开推送后重试";
UnregisteredDevice = "设备未注册,不能使用推送服务";
@ -41,7 +45,7 @@ InvalidServer = "填写的服务器无效,请重试!";
InvalidURL = "输入的URL好像不对劲!";
urlParameter = "携带url参数时点击推送会跳转到这个URL";
urlParameter = "携带url参数时点击推送会跳转到这个URL,支持跳转URL Scheme";
copyParameter = "下拉推送、锁屏界面左滑查看推送时,可以选择复制推送内容\n携带copy参数时将只复制copy参数的值";
@ -62,7 +66,7 @@ defaultArchiveSettings = "默认保存";
archiveNote = "当推送请求URL没有指定 isArchive 参数时,将按照此设置来决定是否保存通知消息";
archiveNotificationMessageTitle = "自动保存通知消息";
archiveNotificationMessage = "当设置 isArchive 值为 1 时,则会自动保存这条推送消息,设置为其他值时,则不会保存。\n如果不指定 isArchive 参数时,则按照默认设置保存消息,可以在 消息->设置->默认保存消息 更改默认保存设置";
archiveNotificationMessage = "当设置 isArchive 值为 1 时,则会自动保存这条推送消息,设置为其他值时,则不会保存。\n如果不指定 isArchive 参数时,则按照默认设置保存消息,可以在 设置->默认保存消息 更改默认保存设置";
notificationSound = "推送铃声";
previewSound = "点击可预览";
@ -122,3 +126,29 @@ deleteFailed = "不能删除,服务器至少需保留一个";
deletedSuccessfully = "删除成功";
resetFailed = "重置失败";
resetFailed2 = "重置失败,没有获取到 DeviceToken";
export = "导出";
import = "导入";
exportOrImport = "导出或导入消息列表";
items = "条消息";
enterKey = "请输入%d位Key";
enterIv = "请输入16位Iv";
encryptionSettings = "加密设置";
algorithm = "算法";
mode = "模式";
copyExample = "复制发送脚本示例";
preview = "预览";
pushNotificationEncryption = "推送加密";
encryptionNotice = "在发送和接收推送时,对推送内容进行加密和解密。这样,推送内容在传输过程中就不会被 Bark 服务器和苹果 APNs 服务器获取或泄露,从而保护你的隐私";
faqUrl = "https://bark.day.app/#/faq";
docUrl = "https://bark.day.app/#/?id=bark";
encryptionUrl = "https://bark.day.app/#/encryption";
deployUrl = "https://bark.day.app/#/deploy";
documentation = "使用文档";
ivComment = "IV可以是随机生成的但如果是随机的就需要放在 iv 参数里传递。";
opensslEncodingComment = "OpenSSL 要求输入的 Key 和 IV 需使用十六进制编码。";
ciphertextComment = "密文可能有特殊字符,所以记得 URL 编码一下。";
consoleComment = "控制台将打印";
keyComment = "必须%d位";

View File

@ -0,0 +1,23 @@
//
// ArchiveSettingRelay.swift
// Bark
//
// Created by huangfeng on 2023/1/30.
// Copyright © 2023 Fin. All rights reserved.
//
import RxCocoa
import UIKit
class ArchiveSettingRelay: NSObject {
static let shared = ArchiveSettingRelay()
let isArchiveRelay: BehaviorRelay<Bool>
override private init() {
self.isArchiveRelay = BehaviorRelay<Bool>(value: ArchiveSettingManager.shared.isArchive)
super.init()
self.isArchiveRelay.subscribe { val in
ArchiveSettingManager.shared.isArchive = val
}.disposed(by: rx.disposeBag)
}
}

View File

@ -17,6 +17,7 @@ class BKColor: NSObject {
public static let darken3 = UIColor(named: "grey_darken3")!
public static let darken4 = UIColor(named: "grey_darken4")!
public static let lighten1 = UIColor(named: "grey_lighten1")!
public static let lighten2 = UIColor(named: "grey_lighten2")!
public static let lighten3 = UIColor(named: "grey_lighten3")!
public static let lighten4 = UIColor(named: "grey_lighten4")!
public static let lighten5 = UIColor(named: "grey_lighten5")!
@ -25,6 +26,7 @@ class BKColor: NSObject {
enum blue {
public static let base = UIColor(named: "blue_base")!
public static let darken1 = UIColor(named: "blue_darken1")!
public static let darken5 = UIColor(named: "blue_darken5")!
}
enum lightBlue {

View File

@ -0,0 +1,40 @@
//
// CryptoSettingManager.swift
// Bark
//
// Created by huangfeng on 2023/3/2.
// Copyright © 2023 Fin. All rights reserved.
//
import Foundation
class CryptoSettingManager: NSObject {
static let shared = CryptoSettingManager()
let defaults = UserDefaults(suiteName: "group.bark")
var fields: CryptoSettingFields? {
get {
guard let data:Data = defaults?.value(forKey: "cryptoSettingFields") as? Data else {
return nil
}
guard let fields = try? JSONDecoder().decode(CryptoSettingFields.self, from: data) else {
return nil
}
return fields
}
set {
guard let newValue = newValue else {
defaults?.removeObject(forKey: "cryptoSettingFields")
return
}
guard let encoded = try? JSONEncoder().encode(newValue) else{
return
}
defaults?.set(encoded, forKey: "cryptoSettingFields")
}
}
override private init() {
super.init()
}
}

View File

@ -0,0 +1,24 @@
//
// CryptoSettingRelay.swift
// Bark
//
// Created by huangfeng on 2023/3/7.
// Copyright © 2023 Fin. All rights reserved.
//
import Foundation
import RxCocoa
class CryptoSettingRelay: NSObject {
static let shared = CryptoSettingRelay()
let fields: BehaviorRelay<CryptoSettingFields?>
override private init() {
self.fields = BehaviorRelay<CryptoSettingFields?>(value: CryptoSettingManager.shared.fields)
super.init()
self.fields.subscribe { val in
CryptoSettingManager.shared.fields = val
}.disposed(by: rx.disposeBag)
}
}

View File

@ -28,10 +28,6 @@ extension UIViewController {
}
}
func NSLocalizedString(_ key: String) -> String {
return NSLocalizedString(key, comment: "")
}
let kNavigationHeight: CGFloat = {
kSafeAreaInsets.top + 44
}()

View File

@ -0,0 +1,33 @@
//
// Error+Extension.swift
// Bark
//
// Created by huangfeng on 2023/3/3.
// Copyright © 2023 Fin. All rights reserved.
//
import Foundation
extension String: Error {}
public enum ApiError: Swift.Error {
case Error(info: String)
case AccountBanned(info: String)
}
extension Swift.Error {
func rawString() -> String {
if let err = self as? String {
return err
}
guard let err = self as? ApiError else {
return self.localizedDescription
}
switch err {
case .Error(let info):
return info
case .AccountBanned(let info):
return info
}
}
}

View File

@ -14,25 +14,6 @@ import RxSwift
import SwiftyJSON
import UIKit
public enum ApiError: Swift.Error {
case Error(info: String)
case AccountBanned(info: String)
}
extension Swift.Error {
func rawString() -> String {
guard let err = self as? ApiError else {
return self.localizedDescription
}
switch err {
case .Error(let info):
return info
case .AccountBanned(let info):
return info
}
}
}
extension Observable where Element: Moya.Response {
/// HTTP
func filterHttpError() -> Observable<Result<Element, ApiError>> {

View File

@ -0,0 +1,13 @@
//
// NSLocalizedString+Extension.swift
// Bark
//
// Created by huangfeng on 2023/3/3.
// Copyright © 2023 Fin. All rights reserved.
//
import Foundation
func NSLocalizedString(_ key: String) -> String {
return NSLocalizedString(key, comment: "")
}

View File

@ -12,7 +12,7 @@ import UIKit
private var prepareForReuseBag: Int8 = 0
@objc public protocol Reusable: class {
@objc public protocol Reusable: AnyObject {
func prepareForReuse()
}
@ -21,10 +21,6 @@ extension UITableViewHeaderFooterView: Reusable {}
extension UICollectionReusableView: Reusable {}
extension Reactive where Base: Reusable {
var prepareForReuse: Observable<Void> {
return Observable.of(sentMessage(#selector(Base.prepareForReuse)).map { _ in }, deallocated).merge()
}
var reuseBag: DisposeBag {
MainScheduler.ensureExecutingOnScheduler()
@ -36,6 +32,7 @@ extension Reactive where Base: Reusable {
objc_setAssociatedObject(base, &prepareForReuseBag, bag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
_ = sentMessage(#selector(Base.prepareForReuse))
.take(until: deallocated)
.subscribe(onNext: { [weak base] _ in
guard let strongBase = base else {
return

View File

@ -0,0 +1,242 @@
//
// CryptoSettingController.swift
// Bark
//
// Created by huangfeng on 2022/11/10.
// Copyright © 2022 Fin. All rights reserved.
//
import RxSwift
import UIKit
class CryptoSettingController: BaseViewController<CryptoSettingViewModel> {
let algorithmFeild = DropBoxView(values: ["AES128", "AES192", "AES256"])
let modeFeild = DropBoxView(values: ["CBC", "ECB"])
let paddingField = DropBoxView(values: ["pkcs7"])
let keyTextField: BorderTextField = {
let textField = BorderTextField(title: "Key")
textField.font = UIFont.systemFont(ofSize: 14)
textField.placeholder = String(format: NSLocalizedString("enterKey"), 16)
return textField
}()
let ivTextField: BorderTextField = {
let textField = BorderTextField(title: "IV")
textField.font = UIFont.systemFont(ofSize: 14)
textField.placeholder = NSLocalizedString("enterIv")
return textField
}()
let doneButton: BKButton = {
let btn = BKButton()
btn.setTitle(NSLocalizedString("done"), for: .normal)
btn.setTitleColor(BKColor.lightBlue.darken3, for: .normal)
btn.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
btn.fontSize = 14
return btn
}()
let copyButton: UIButton = {
let btn = GradientButton()
btn.setTitle(NSLocalizedString("copyExample"), for: .normal)
btn.setTitleColor(UIColor.white, for: .normal)
btn.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .medium)
btn.layer.cornerRadius = 8
btn.clipsToBounds = true
btn.applyGradient(
withColours: [
UIColor(r255: 36, g255: 51, b255: 236),
UIColor(r255: 70, g255: 44, b255: 233),
],
gradientOrientation: .horizontal
)
return btn
}()
let scrollView = UIScrollView()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func makeUI() {
self.title = NSLocalizedString("encryptionSettings")
self.navigationItem.setRightBarButtonItem(item: UIBarButtonItem(customView: doneButton))
self.view.addSubview(scrollView)
scrollView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
func getTitleLabel(title: String) -> UILabel {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.textColor = BKColor.grey.darken4
label.text = title
return label
}
let algorithmLabel = getTitleLabel(title: NSLocalizedString("algorithm"))
let modeLabel = getTitleLabel(title: NSLocalizedString("mode"))
let paddingLabel = getTitleLabel(title: "Padding")
let keyLabel = getTitleLabel(title: "Key")
let ivLabel = getTitleLabel(title: "Iv")
self.scrollView.addSubview(algorithmLabel)
self.scrollView.addSubview(algorithmFeild)
self.scrollView.addSubview(modeLabel)
self.scrollView.addSubview(modeFeild)
self.scrollView.addSubview(paddingLabel)
self.scrollView.addSubview(paddingField)
self.scrollView.addSubview(keyLabel)
self.scrollView.addSubview(keyTextField)
self.scrollView.addSubview(ivLabel)
self.scrollView.addSubview(ivTextField)
self.scrollView.addSubview(copyButton)
self.view.backgroundColor = UIColor.white
algorithmLabel.snp.makeConstraints { make in
make.top.equalTo(24)
make.left.equalTo(24)
}
algorithmFeild.snp.makeConstraints { make in
make.top.equalTo(algorithmLabel.snp.bottom).offset(5)
make.left.equalTo(20)
make.right.equalTo(-20)
make.height.equalTo(45)
make.width.equalToSuperview().offset(-40)
}
modeLabel.snp.makeConstraints { make in
make.top.equalTo(algorithmFeild.snp.bottom).offset(20)
make.left.equalTo(algorithmLabel)
}
modeFeild.snp.makeConstraints { make in
make.left.right.height.equalTo(algorithmFeild)
make.top.equalTo(modeLabel.snp.bottom).offset(5)
}
paddingLabel.snp.makeConstraints { make in
make.top.equalTo(modeFeild.snp.bottom).offset(20)
make.left.equalTo(algorithmLabel)
}
paddingField.snp.makeConstraints { make in
make.left.right.height.equalTo(modeFeild)
make.top.equalTo(paddingLabel.snp.bottom).offset(5)
}
keyLabel.snp.makeConstraints { make in
make.top.equalTo(paddingField.snp.bottom).offset(20)
make.left.equalTo(algorithmLabel)
}
keyTextField.snp.makeConstraints { make in
make.left.right.height.equalTo(paddingField)
make.top.equalTo(keyLabel.snp.bottom).offset(5)
}
ivLabel.snp.makeConstraints { make in
make.top.equalTo(keyTextField.snp.bottom).offset(20)
make.left.equalTo(algorithmLabel)
}
ivTextField.snp.makeConstraints { make in
make.left.right.height.equalTo(keyTextField)
make.top.equalTo(ivLabel.snp.bottom).offset(5)
}
copyButton.snp.makeConstraints { make in
make.left.equalTo(ivTextField)
make.right.equalTo(ivTextField)
make.height.equalTo(42)
make.top.equalTo(ivTextField.snp.bottom).offset(25)
make.bottom.equalToSuperview().offset(-20)
}
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(resign)))
}
@objc func resign() {
self.view.endEditing(true)
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = BKColor.white
}
override func bindViewModel() {
func getFieldValues() -> CryptoSettingFields {
return CryptoSettingFields(
algorithm: self.algorithmFeild.currentValue!,
mode: self.modeFeild.currentValue!,
padding: self.paddingField.currentValue!,
key: self.keyTextField.text,
iv: self.ivTextField.text
)
}
let output = viewModel.transform(input: CryptoSettingViewModel.Input(
algorithmChanged: self.algorithmFeild
.rx
.currentValueChanged
.compactMap { $0 }
.asDriver(onErrorDriveWith: .empty()),
copyScript: copyButton
.rx
.tap
.map { getFieldValues() }
.asDriver(onErrorDriveWith: .empty()),
done: doneButton
.rx
.tap
.map { getFieldValues() }
.asDriver(onErrorDriveWith: .empty())
))
output.initial.drive(onNext: { [weak self] val in
self?.algorithmFeild.values = val.algorithmList.map { $0.rawValue }
self?.modeFeild.values = val.modeList
self?.paddingField.values = val.paddingList
if let fields = val.initialFields {
self?.algorithmFeild.currentValue = fields.algorithm
self?.modeFeild.currentValue = fields.mode
self?.paddingField.currentValue = fields.padding
self?.keyTextField.text = fields.key
self?.ivTextField.text = fields.iv
}
}).disposed(by: rx.disposeBag)
output.modeListChanged
.drive(self.modeFeild.rx.values)
.disposed(by: rx.disposeBag)
output.paddingListChanged
.drive(self.paddingField.rx.values)
.disposed(by: rx.disposeBag)
output.keyLenghtChanged.drive(onNext: { [weak self] keyLenght in
self?.keyTextField.placeholder = String(format: NSLocalizedString("enterKey"), keyLenght)
}).disposed(by: rx.disposeBag)
output.showSnackbar.drive(onNext: { text in
HUDError(text)
}).disposed(by: rx.disposeBag)
output.done.drive(onNext: { [weak self] in
self?.navigationController?.dismiss(animated: true)
}).disposed(by: rx.disposeBag)
output.copy.drive(onNext: { text in
UIPasteboard.general.string = text
HUDSuccess(NSLocalizedString("Copy"))
}).disposed(by: rx.disposeBag)
}
}

View File

@ -0,0 +1,150 @@
//
// CryptoSettingViewModel.swift
// Bark
//
// Created by huangfeng on 2022/11/10.
// Copyright © 2022 Fin. All rights reserved.
//
import CryptoSwift
import Foundation
import RxCocoa
import RxSwift
class CryptoSettingViewModel: ViewModel, ViewModelType {
struct Input {
let algorithmChanged: Driver<String>
let copyScript: Driver<CryptoSettingFields>
let done: Driver<CryptoSettingFields>
}
struct Output {
let initial: Driver<(algorithmList: [Algorithm], modeList: [String], paddingList: [String], initialFields: CryptoSettingFields?)>
let modeListChanged: Driver<[String]>
let paddingListChanged: Driver<[String]>
let keyLenghtChanged: Driver<Int>
let showSnackbar: Driver<String>
let done: Driver<Void>
let copy: Driver<String>
}
struct Dependencies {
let settingFieldRelay: BehaviorRelay<CryptoSettingFields?>
let deviceKey: Driver<String>
let serverAddress: Driver<String>
}
private let dependencies: Dependencies
init(dependencies: Dependencies =
Dependencies(
settingFieldRelay: CryptoSettingRelay.shared.fields,
// Key just
deviceKey: Driver.just(ServerManager.shared.currentServer.key),
serverAddress: Driver.just(ServerManager.shared.currentServer.address)
)
) {
self.dependencies = dependencies
}
func transform(input: Input) -> Output {
let showSnackbar = PublishRelay<String>()
let modeList = input
.algorithmChanged
.compactMap { Algorithm(rawValue: $0) }
.map { $0.modes }
let keyLenght =
Driver.merge([
Driver.just(dependencies.settingFieldRelay.value)
.compactMap { $0 }
.compactMap { Algorithm(rawValue: $0.algorithm)?.keyLenght },
input
.algorithmChanged
.compactMap { Algorithm(rawValue: $0)?.keyLenght },
])
//
let done = input.done
.filter { fields in
do {
_ = try AESCryptoModel(cryptoFields: fields)
return true
}
catch {
showSnackbar.accept(error.rawString())
return false
}
}
done.drive(onNext: { [weak self] fields in
//
self?.dependencies.settingFieldRelay.accept(fields)
}).disposed(by: rx.disposeBag)
let copyScript = input.copyScript
.filter { [weak self] fields in
do {
_ = try AESCryptoModel(cryptoFields: fields)
//
self?.dependencies.settingFieldRelay.accept(fields)
return true
}
catch {
showSnackbar.accept(error.rawString())
return false
}
}
let copy = Driver.combineLatest(copyScript, dependencies.deviceKey, dependencies.serverAddress)
.map { fields, deviceKey,serverAddress in
let key = fields.key ?? ""
let iv = fields.iv ?? ""
return
"""
#!/usr/bin/env bash
# Documentation: \(NSLocalizedString("encryptionUrl"))
set -e
# bark key
deviceKey='\(deviceKey)'
# push payload
json='{"body": "test", "sound": "birdsong"}'
# \(String(format: NSLocalizedString("keyComment"), Int(fields.algorithm.suffix(3))! / 8))
key='\(key)'
# \(NSLocalizedString("ivComment"))
iv='\(iv)'
# \(NSLocalizedString("opensslEncodingComment"))
key=$(printf $key | xxd -ps -c 200)
iv=$(printf $iv | xxd -ps -c 200)
ciphertext=$(echo -n $json | openssl enc -aes-\(fields.algorithm.suffix(3))-\(fields.mode.lowercased()) -K $key \(iv.count > 0 ? "-iv $iv " : "")| base64)
# \(NSLocalizedString("consoleComment")) "\((try? AESCryptoModel(cryptoFields: fields).encrypt(text: "{\"body\": \"test\", \"sound\": \"birdsong\"}")) ?? "")"
echo $ciphertext
# \(NSLocalizedString("ciphertextComment"))
curl --data-urlencode "ciphertext=$ciphertext"\( iv.count == 0 ? "" : " --data-urlencode \"iv=\(iv)\"") \(serverAddress)/$deviceKey
"""
}
return Output(
initial: Driver.just((
algorithmList: [Algorithm.aes128, Algorithm.aes192, Algorithm.aes256],
modeList: ["CBC", "ECB"],
paddingList: ["okcs7"],
initialFields: dependencies.settingFieldRelay.value
)),
modeListChanged: modeList,
paddingListChanged: Driver.just(["pkcs7"]),
keyLenghtChanged: keyLenght,
showSnackbar: showSnackbar.asDriver(onErrorDriveWith: .empty()),
done: done.map { _ in () },
copy: copy
)
}
}

View File

@ -18,7 +18,7 @@ class GroupFilterViewController: BaseViewController<GroupFilterViewModel> {
let doneButton: BKButton = {
let btn = BKButton()
btn.setTitle(NSLocalizedString("done"), for: .normal)
btn.setTitleColor(Color.lightBlue.darken3, for: .normal)
btn.setTitleColor(BKColor.lightBlue.darken3, for: .normal)
btn.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
btn.fontSize = 14
return btn

View File

@ -202,7 +202,10 @@ class HomeViewController: BaseViewController<HomeViewModel> {
else if let viewModel = viewModel as? SoundsViewModel {
viewController = SoundsViewController(viewModel: viewModel)
}
else if let viewModel = viewModel as? CryptoSettingViewModel {
self.navigationController?.present(BarkNavigationController(rootViewController: CryptoSettingController(viewModel: viewModel)), animated: true)
return
}
if let viewController = viewController {
self.navigationController?.pushViewController(viewController, animated: true)
}

View File

@ -74,6 +74,13 @@ class HomeViewModel: ViewModel, ViewModelType {
queryParameter: "group=groupName",
image: UIImage(named: "group")
),
PreviewModel(
body: NSLocalizedString("pushNotificationEncryption"),
notice: NSLocalizedString("encryptionNotice"),
queryParameter: "ciphertext=ciphertext",
moreInfo: NSLocalizedString("encryptionSettings"),
moreViewModel: CryptoSettingViewModel()
),
PreviewModel(
body: NSLocalizedString("interruptionLevel"),
notice: NSLocalizedString("interruptionLevelNotice"),

View File

@ -169,11 +169,6 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
self?.alertMessage(message: message)
}).disposed(by: rx.disposeBag)
// messageURL
output.urlTap.drive(onNext: { url in
Client.shared.openUrl(url: url)
}).disposed(by: rx.disposeBag)
//
output.groupFilter
.drive(onNext: { [weak self] groupModel in
@ -190,7 +185,7 @@ class MessageListViewController: BaseViewController<MessageListViewModel> {
func alertMessage(message: String) {
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let copyAction = UIAlertAction(title: NSLocalizedString("Copy2"), style: .default, handler: { [weak self]
let copyAction = UIAlertAction(title: NSLocalizedString("CopyAll"), style: .default, handler: { [weak self]
(_: UIAlertAction) -> Void in
UIPasteboard.general.string = message
self?.showSnackbar(text: NSLocalizedString("Copy"))

View File

@ -27,7 +27,6 @@ class MessageListViewModel: ViewModel, ViewModelType {
var messages: Driver<[MessageSection]>
var refreshAction: Driver<MJRefreshAction>
var alertMessage: Driver<String>
var urlTap: Driver<URL>
var groupFilter: Driver<GroupFilterViewModel>
var title: Driver<String>
}
@ -178,17 +177,6 @@ class MessageListViewModel: ViewModel, ViewModelType {
}
}).disposed(by: rx.disposeBag)
// cell url
let urlTap = messagesRelay.flatMapLatest { section -> Observable<String> in
if let section = section.first {
let taps = section.messages.compactMap { model -> Observable<String> in
model.urlTap.asObservable()
}
return Observable.merge(taps)
}
return .empty()
}
.compactMap { URL(string: $0) } // url
//
input.delete.drive(onNext: { [weak self] type in
@ -260,7 +248,6 @@ class MessageListViewModel: ViewModel, ViewModelType {
messages: messagesRelay.asDriver(onErrorJustReturn: []),
refreshAction: refreshAction.asDriver(),
alertMessage: alertMessage,
urlTap: urlTap.asDriver(onErrorDriveWith: .empty()),
groupFilter: groupFilter.asDriver(),
title: titleRelay.asDriver()
)

View File

@ -7,9 +7,13 @@
//
import Material
import RxCocoa
import RxDataSources
import RxSwift
import UIKit
class MessageSettingsViewController: BaseViewController<MessageSettingsViewModel> {
import UniformTypeIdentifiers
class MessageSettingsViewController: BaseViewController<MessageSettingsViewModel>, UIDocumentPickerDelegate {
let tableView: UITableView = {
let tableView = UITableView()
tableView.separatorStyle = .none
@ -32,12 +36,93 @@ class MessageSettingsViewController: BaseViewController<MessageSettingsViewModel
make.edges.equalToSuperview()
}
}
///
enum BackupOrRestoreActionEnum {
case export, `import`(data: Data)
}
/// UIDocumentPickerDelegate delegate
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else { return }
let canAccessingResource = url.startAccessingSecurityScopedResource()
guard canAccessingResource else { return }
let fileCoordinator = NSFileCoordinator()
let err = NSErrorPointer(nilLiteral: ())
fileCoordinator.coordinate(readingItemAt: url, error: err) { url in
if let data = try? Data(contentsOf: url) {
self.backupOrRestoreActionRelay.accept(.import(data: data))
}
}
url.stopAccessingSecurityScopedResource()
}
///
let backupOrRestoreActionRelay = PublishRelay<BackupOrRestoreActionEnum>()
///
func getBackupOrRestoreAction() -> (Driver<Void>, Driver<Data>) {
let backupOrRestoreAction = self.tableView.rx
.modelSelected(MessageSettingItem.self)
.filter { item in
if case MessageSettingItem.backup = item {
return true
}
return false
}
.flatMapLatest { [weak self] _ in
guard let strongSelf = self else {
return Observable<BackupOrRestoreActionEnum>.empty()
}
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: NSLocalizedString("export"), style: .default, handler: { _ in
strongSelf.backupOrRestoreActionRelay.accept(.export)
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("import"), style: .default, handler: { [weak self] _ in
if #available(iOS 14.0, *) {
let supportedType: [UTType] = [UTType.json]
let pickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedType, asCopy: false)
pickerViewController.delegate = self
self?.present(pickerViewController, animated: true, completion: nil)
}
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel"), style: .cancel, handler: nil))
strongSelf.navigationController?.present(alertController, animated: true, completion: nil)
return strongSelf.backupOrRestoreActionRelay.asObservable()
}
let backupAction = backupOrRestoreAction
.filter { action in
if case .export = action { return true } else { return false }
}
.map { _ in () }
.asDriver(onErrorDriveWith: .empty())
let restoreAction = backupOrRestoreAction
.compactMap { action in
if case let .import(data) = action { return data } else { return nil }
}
.asDriver(onErrorDriveWith: .empty())
return (backupAction, restoreAction)
}
override func bindViewModel() {
let actions = getBackupOrRestoreAction()
let output = viewModel.transform(
input: MessageSettingsViewModel.Input(
itemSelected: self.tableView.rx.modelSelected(MessageSettingItem.self).asDriver(),
deviceToken: Client.shared.deviceToken.asDriver()
deviceToken: Client.shared.deviceToken.asDriver(),
backupAction: actions.0,
restoreAction: actions.1,
viewDidAppear: self.rx.methodInvoked(#selector(viewDidAppear(_:)))
.map { _ in () },
archiveSettingRelay: ArchiveSettingRelay.shared.isArchiveRelay
)
)
@ -52,6 +137,12 @@ class MessageSettingsViewController: BaseViewController<MessageSettingsViewModel
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(iCloudStatusCell.self)") {
return cell
}
case let .backup(viewModel):
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(MutableTextCell.self)") as? MutableTextCell {
cell.textLabel?.textColor = BKColor.blue.darken1
cell.bindViewModel(model: viewModel)
return cell
}
case let .archiveSetting(viewModel):
if let cell = tableView.dequeueReusableCell(withIdentifier: "\(ArchiveSettingCell.self)") as? ArchiveSettingCell {
cell.bindViewModel(model: viewModel)
@ -80,17 +171,47 @@ class MessageSettingsViewController: BaseViewController<MessageSettingsViewModel
return UITableViewCell()
}
//
output.settings
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: rx.disposeBag)
// URL
output.openUrl.drive { [weak self] url in
self?.navigationController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil)
}.disposed(by: rx.disposeBag)
// deviceToken
output.copyDeviceToken.drive { [weak self] deviceToken in
UIPasteboard.general.string = deviceToken
self?.showSnackbar(text: NSLocalizedString("Copy"))
}.disposed(by: rx.disposeBag)
//
output.exportData.drive { [weak self] data in
let fileManager = FileManager.default
let tempDirectoryURL = fileManager.temporaryDirectory
let fileName = "bark_messages_\(Date().formatString(format: "yyyy_MM_dd_HH_mm_ss")).json"
let linkURL = tempDirectoryURL.appendingPathComponent(fileName)
do {
// temp
try fileManager
.contentsOfDirectory(at: tempDirectoryURL, includingPropertiesForKeys: nil, options: .skipsSubdirectoryDescendants)
.forEach { file in
try? fileManager.removeItem(atPath: file.path)
}
//
try data.write(to: linkURL)
}
catch {
// Hope nothing happens
}
let activityController = UIActivityViewController(activityItems: [linkURL], applicationActivities: nil)
self?.navigationController?.present(activityController, animated: true, completion: nil)
}.disposed(by: rx.disposeBag)
}
}

View File

@ -8,29 +8,90 @@
import Foundation
import Material
import RealmSwift
import RxCocoa
import RxDataSources
import RxSwift
import SwiftyJSON
class MessageSettingsViewModel: ViewModel, ViewModelType {
struct Input {
var itemSelected: Driver<MessageSettingItem>
var deviceToken: Driver<String?>
var backupAction: Driver<Void>
var restoreAction: Driver<Data>
var viewDidAppear: Observable<Void>
var archiveSettingRelay: BehaviorRelay<Bool>
}
struct Output {
var settings: Driver<[SectionModel<String, MessageSettingItem>]>
var openUrl: Driver<URL>
var copyDeviceToken: Driver<String>
var exportData: Driver<Data>
}
func transform(input: Input) -> Output {
let restoreSuccess = input
.restoreAction
.compactMap { data -> Void? in
guard let json = try? JSON(data: data), let arr = json.array else {
return nil
}
guard let realm = try? Realm() else {
return nil
}
try? realm.write {
for message in arr {
guard let id = message["id"].string else {
continue
}
guard let createDate = message["createDate"].int64 else {
continue
}
let title = message["title"].string
let body = message["body"].string
let url = message["url"].string
let group = message["group"].string
let messageObject = Message()
messageObject.id = id
messageObject.title = title
messageObject.body = body
messageObject.url = url
messageObject.group = group
messageObject.createDate = Date(timeIntervalSince1970: TimeInterval(createDate))
realm.add(messageObject, update: .modified)
}
}
return ()
}.asObservable().share()
let settings: [MessageSettingItem] = {
var settings = [MessageSettingItem]()
settings.append(.label(text: "iCloud"))
settings.append(.iCloudStatus)
settings.append(.label(text: NSLocalizedString("iCloudSync")))
settings.append(.archiveSetting(viewModel: ArchiveSettingCellViewModel(on: ArchiveSettingManager.shared.isArchive)))
settings.append(.backup(viewModel: MutableTextCellViewModel(
title: "\(NSLocalizedString("export"))/\(NSLocalizedString("import"))",
text: Observable.merge([restoreSuccess, input.viewDidAppear])
.map { _ in
if let realm = try? Realm() {
return realm.objects(Message.self)
.filter("isDeleted != true")
.count
}
return 0
}
.map { count in
"\(count) \(NSLocalizedString("items"))"
}
.asDriver(onErrorDriveWith: .empty()))
))
settings.append(.label(text: NSLocalizedString("exportOrImport")))
settings.append(.archiveSetting(viewModel: ArchiveSettingCellViewModel(on: input.archiveSettingRelay)))
settings.append(.label(text: NSLocalizedString("archiveNote")))
settings.append(.label(text: NSLocalizedString("info")))
@ -65,36 +126,17 @@ class MessageSettingsViewModel: ViewModel, ViewModelType {
title: NSLocalizedString("faq"),
text: nil,
textColor: nil,
url: URL(string: "https://day.app/2021/06/barkfaq/")))
url: URL(string: NSLocalizedString("faqUrl"))))
settings.append(.spacer(height: 0.5, color: BKColor.grey.lighten4))
settings.append(.detail(
title: NSLocalizedString("appSC"),
title: NSLocalizedString("documentation"),
text: nil,
textColor: nil,
url: URL(string: "https://github.com/Finb/Bark")))
settings.append(.spacer(height: 0.5, color: BKColor.grey.lighten4))
settings.append(.detail(
title: NSLocalizedString("backendSC"),
text: nil,
textColor: nil,
url: URL(string: "https://github.com/Finb/bark-server")))
url: URL(string: NSLocalizedString("docUrl"))))
return settings
}()
settings.compactMap { item -> ArchiveSettingCellViewModel? in
if case let MessageSettingItem.archiveSetting(viewModel) = item {
return viewModel
}
return nil
}
.first?
.on
.subscribe(onNext: { on in
ArchiveSettingManager.shared.isArchive = on
}).disposed(by: rx.disposeBag)
let openUrl = input.itemSelected.compactMap { item -> URL? in
if case let MessageSettingItem.detail(_, _, _, url) = item {
return url
@ -112,11 +154,31 @@ class MessageSettingsViewModel: ViewModel, ViewModelType {
return nil
}
//
let exportSuccess = input.backupAction
.asObservable()
.subscribe(on: ConcurrentDispatchQueueScheduler(qos: .userInitiated))
.compactMap { _ in
if let realm = try? Realm() {
let messages = realm.objects(Message.self)
.filter("isDeleted != true")
.sorted(byKeyPath: "createDate", ascending: false)
var arr = [[String: AnyObject]]()
for message in messages {
arr.append(message.toDictionary())
}
return try? JSON(arr).rawData(options: JSONSerialization.WritingOptions.prettyPrinted)
}
return nil
}
return Output(
settings: Driver<[SectionModel<String, MessageSettingItem>]>
.just([SectionModel(model: "model", items: settings)]),
openUrl: openUrl,
copyDeviceToken: copyDeviceToken)
copyDeviceToken: copyDeviceToken,
exportData: exportSuccess.asDriver(onErrorDriveWith: .empty()))
}
}
@ -129,6 +191,8 @@ enum MessageSettingItem {
case archiveSetting(viewModel: ArchiveSettingCellViewModel)
// cell
case detail(title: String?, text: String?, textColor: UIColor?, url: URL?)
//
case backup(viewModel: MutableTextCellViewModel)
// deviceToken
case deviceToken(viewModel: MutableTextCellViewModel)
// 线

View File

@ -38,7 +38,7 @@ class NewServerViewModel: ViewModel, ViewModelType {
let showSnackbar = PublishRelay<String>()
let notice = input.noticeClick
.map { URL(string: "https://day.app/2018/06/bark-server-document/")! }
.map { URL(string: NSLocalizedString("deployUrl"))! }
.asDriver()
input.viewDidAppear

View File

@ -87,6 +87,15 @@ class ServerListViewModel: ViewModel, ViewModelType {
.bind(to: showSnackbar)
.disposed(by: rx.disposeBag)
// key deviceToken, 使
resetServer.filter { ($0.2?.count ?? 0) > 0 && $0.0.key.count > 0 }.flatMapLatest {
BarkApi.provider
.request(.register(address: $0.0.address, key: $0.0.key, devicetoken: "deleted"))
.filterResponseError()
}
.subscribe()
.disposed(by: rx.disposeBag)
//
let serverReseted = resetServer.filter { ($0.2?.count ?? 0) > 0 }
.flatMapLatest { r -> Observable<Result<JSON, ApiError>> in

View File

@ -59,9 +59,9 @@ class SoundsViewController: BaseViewController<SoundsViewModel> {
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: rx.disposeBag)
output.copyNameAction.drive(onNext: { name in
output.copyNameAction.drive(onNext: { [weak self] name in
UIPasteboard.general.string = name.trimmingCharacters(in: .whitespacesAndNewlines)
self.navigationController?.showSnackbar(text: NSLocalizedString("Copy"))
self?.navigationController?.showSnackbar(text: NSLocalizedString("Copy"))
}).disposed(by: rx.disposeBag)
output.playAction.drive(onNext: { url in

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>IntentsRestrictedWhileLocked</key>
<array/>
<key>IntentsSupported</key>
<array>
<string>INSendMessageIntent</string>
</array>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.intents-service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).IntentHandler</string>
</dict>
</dict>
</plist>

View File

@ -1,122 +0,0 @@
//
// IntentHandler.swift
// Intent
//
// Created by huangfeng on 2021/9/26.
// Copyright © 2021 Fin. All rights reserved.
//
import Intents
// As an example, this class is set up to handle Message intents.
// You will want to replace this or add other intents as appropriate.
// The intents you wish to handle must be declared in the extension's Info.plist.
// You can test your example integration by saying things to Siri like:
// "Send a message using <myApp>"
// "<myApp> John saying hello"
// "Search for messages in <myApp>"
class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling {
override func handler(for intent: INIntent) -> Any {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.
return self
}
// MARK: - INSendMessageIntentHandling
// Implement resolution methods to provide additional information about your intent (optional).
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) {
if let recipients = intent.recipients {
// If no recipients were provided we'll need to prompt for a value.
if recipients.count == 0 {
completion([INSendMessageRecipientResolutionResult.needsValue()])
return
}
var resolutionResults = [INSendMessageRecipientResolutionResult]()
for recipient in recipients {
let matchingContacts = [recipient] // Implement your contact matching logic here to create an array of matching contacts
switch matchingContacts.count {
case 2 ... Int.max:
// We need Siri's help to ask user to pick one from the matches.
resolutionResults += [INSendMessageRecipientResolutionResult.disambiguation(with: matchingContacts)]
case 1:
// We have exactly one matching contact
resolutionResults += [INSendMessageRecipientResolutionResult.success(with: recipient)]
case 0:
// We have no contacts matching the description provided
resolutionResults += [INSendMessageRecipientResolutionResult.unsupported()]
default:
break
}
}
completion(resolutionResults)
} else {
completion([INSendMessageRecipientResolutionResult.needsValue()])
}
}
func resolveContent(for intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
if let text = intent.content, !text.isEmpty {
completion(INStringResolutionResult.success(with: text))
} else {
completion(INStringResolutionResult.needsValue())
}
}
// Once resolution is completed, perform validation on the intent and provide confirmation (optional).
func confirm(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
// Verify user is authenticated and your app is ready to send a message.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
let response = INSendMessageIntentResponse(code: .ready, userActivity: userActivity)
completion(response)
}
// Handle the completed intent (required).
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
// Implement your application logic to send a message here.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
completion(response)
}
// Implement handlers for each intent you wish to handle. As an example for messages, you may wish to also handle searchForMessages and setMessageAttributes.
// MARK: - INSearchForMessagesIntentHandling
func handle(intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) {
// Implement your application logic to find a message that matches the information in the intent.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
// Initialize with found message's attributes
response.messages = [INMessage(
identifier: "identifier",
content: "I am so excited about SiriKit!",
dateSent: Date(),
sender: INPerson(personHandle: INPersonHandle(value: "sarah@example.com", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil, contactIdentifier: nil, customIdentifier: nil),
recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil, contactIdentifier: nil, customIdentifier: nil)]
)]
completion(response)
}
// MARK: - INSetMessageAttributeIntentHandling
func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
// Implement your application logic to set the message attribute here.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
completion(response)
}
}

101
Model/Algorithm.swift Normal file
View File

@ -0,0 +1,101 @@
//
// Algorithm.swift
// Bark
//
// Created by huangfeng on 2023/2/23.
// Copyright © 2023 Fin. All rights reserved.
//
import CryptoSwift
import Foundation
enum Algorithm: String {
case aes128 = "AES128"
case aes192 = "AES192"
case aes256 = "AES256"
var modes: [String] {
switch self {
case .aes128, .aes192, .aes256:
return ["CBC", "ECB"]
}
}
var paddings: [String] {
switch self {
case .aes128, .aes192, .aes256:
return ["pkcs7"]
}
}
var keyLenght: Int {
switch self {
case .aes128:
return 16
case .aes192:
return 24
case .aes256:
return 32
}
}
}
struct CryptoSettingFields: Codable {
let algorithm: String
let mode: String
let padding: String
let key: String?
var iv: String?
}
struct AESCryptoModel {
let key: String
let mode: BlockMode
let padding: Padding
let aes: AES
init(cryptoFields: CryptoSettingFields) throws {
guard let algorithm = Algorithm(rawValue: cryptoFields.algorithm) else {
throw "Invalid algorithm"
}
guard let key = cryptoFields.key else {
throw "Key is missing"
}
guard algorithm.keyLenght == key.count else {
throw String(format: NSLocalizedString("enterKey"), algorithm.keyLenght)
}
var iv = ""
if ["CBC"].contains(cryptoFields.mode) {
if let ivField = cryptoFields.iv, ivField.count == 16 {
iv = ivField
}
else {
throw NSLocalizedString("enterIv")
}
}
let mode: BlockMode
switch cryptoFields.mode {
case "CBC":
mode = CBC(iv: iv.bytes)
case "ECB":
mode = ECB()
default:
throw "Invalid Mode"
}
self.key = key
self.mode = mode
self.padding = Padding.pkcs7
self.aes = try AES(key: key.bytes, blockMode: self.mode, padding: self.padding)
}
func encrypt(text: String) throws -> String {
return try aes.encrypt(Array(text.utf8)).toBase64()
}
func decrypt(ciphertext: String) throws -> String {
return String(data: Data(try aes.decrypt(Array(base64: ciphertext))), encoding: .utf8) ?? ""
}
}

View File

@ -0,0 +1,40 @@
//
// Object+Dictionary.swift
// Bark
//
// Created by huangfeng on 2022/10/20.
// Copyright © 2022 Fin. All rights reserved.
//
import Foundation
import RealmSwift
extension Object {
func toDictionary() -> [String: AnyObject] {
let properties = self.objectSchema.properties.map { $0.name }
var dicProps = [String: AnyObject]()
for (key, value) in self.dictionaryWithValues(forKeys: properties) {
if let value = value as? ListBase {
dicProps[key] = value.toArray1() as AnyObject
} else if let value = value as? Object {
dicProps[key] = value.toDictionary() as AnyObject
} else if let value = value as? Date {
dicProps[key] = Int64(value.timeIntervalSince1970) as AnyObject
} else {
dicProps[key] = value as AnyObject
}
}
return dicProps
}
}
extension ListBase {
func toArray1() -> [AnyObject] {
var _toArray = [AnyObject]()
for i in 0 ..< self._rlmArray.count {
let obj = unsafeBitCast(self._rlmArray[i], to: Object.self)
_toArray.append(obj.toDictionary() as AnyObject)
}
return _toArray
}
}

View File

@ -10,8 +10,10 @@ import Intents
import Kingfisher
import MobileCoreServices
import RealmSwift
import SwiftyJSON
import UIKit
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
lazy var realm: Realm? = {
@ -232,14 +234,75 @@ class NotificationService: UNNotificationServiceExtension {
}
}
func decrypt(ciphertext: String, iv: String? = nil) throws -> [String: Any] {
guard var fields = CryptoSettingManager.shared.fields else {
throw "No encryption key set"
}
if let iv = iv {
// Support using specified IV parameter for decryption
fields.iv = iv
}
let aes = try AESCryptoModel(cryptoFields: fields)
let json = try aes.decrypt(ciphertext: ciphertext)
guard let data = json.data(using: .utf8), let map = JSON(data).dictionaryObject else {
throw "JSON parsing failed"
}
return map
}
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> ()) {
guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
contentHandler(request.content)
return
}
let userInfo = bestAttemptContent.userInfo
var userInfo = bestAttemptContent.userInfo
// 使 bestAttemptContent
if let ciphertext = userInfo["ciphertext"] as? String {
do {
var map = try decrypt(ciphertext: ciphertext, iv: userInfo["iv"] as? String)
for (key, val) in map {
// key
map[key.lowercased()] = val
}
var alert = [String: Any]()
if let title = map["title"] as? String {
bestAttemptContent.title = title
alert["title"] = title
}
if let body = map["body"] as? String {
bestAttemptContent.body = body
alert["body"] = body
}
if let group = map["group"] as? String {
bestAttemptContent.threadIdentifier = group
}
if var sound = map["sound"] as? String {
if !sound.hasSuffix(".caf") {
sound = "\(sound).caf"
}
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: sound))
}
if let badge = map["badge"] as? Int {
bestAttemptContent.badge = badge as NSNumber
}
map["aps"] = ["alert": alert]
userInfo = map
bestAttemptContent.userInfo = userInfo
}
catch {
bestAttemptContent.body = "Decryption Failed"
bestAttemptContent.userInfo = ["aps": ["alert": ["body": bestAttemptContent.body]]]
contentHandler(bestAttemptContent)
return
}
}
//
if #available(iOSApplicationExtension 15.0, *) {
if let level = userInfo["level"] as? String {

17
Podfile
View File

@ -15,6 +15,8 @@ def pods
pod 'DeviceKit'
pod 'DefaultsKit', :git => 'https://github.com/nmdias/DefaultsKit'
pod 'IceCream'
pod 'CryptoSwift'
pod 'IQKeyboardManagerSwift'
pod 'RxSwift'
pod 'RxCocoa'
@ -25,6 +27,7 @@ def pods
pod 'MJRefresh'
pod 'Kingfisher'
pod 'MercariQRScanner', :git => 'https://github.com/Finb/QRScanner'
pod 'DropDown'
end
target 'Bark' do
@ -40,4 +43,18 @@ end
target 'NotificationServiceExtension' do
pod 'IceCream'
pod 'Kingfisher'
pod 'CryptoSwift'
pod 'SwiftyJSON'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < 13.0
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
end
end
end

View File

@ -1,11 +1,14 @@
PODS:
- Alamofire (5.4.4)
- CryptoSwift (1.6.0)
- DefaultsKit (0.2.0)
- DeviceKit (4.5.0)
- Differentiator (5.0.0)
- DropDown (2.3.13)
- FDFullscreenPopGesture (1.1)
- IceCream (2.0.4):
- RealmSwift (< 10.8.0)
- IQKeyboardManagerSwift (6.5.10)
- Kingfisher (6.3.1)
- KVOController (1.2.0)
- Material (3.1.8):
@ -48,10 +51,13 @@ PODS:
- SwiftyJSON (5.0.1)
DEPENDENCIES:
- CryptoSwift
- DefaultsKit (from `https://github.com/nmdias/DefaultsKit`)
- DeviceKit
- DropDown
- FDFullscreenPopGesture
- IceCream
- IQKeyboardManagerSwift
- Kingfisher
- KVOController
- Material
@ -71,10 +77,13 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- Alamofire
- CryptoSwift
- DeviceKit
- Differentiator
- DropDown
- FDFullscreenPopGesture
- IceCream
- IQKeyboardManagerSwift
- Kingfisher
- KVOController
- Material
@ -110,11 +119,14 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS:
Alamofire: f3b09a368f1582ab751b3fff5460276e0d2cf5c9
CryptoSwift: 562f8eceb40e80796fffc668b0cad9313284cfa6
DefaultsKit: 6c767941b2c3fe34c4d70e300e6c3b80c94964dd
DeviceKit: b68cded46afdaefc5d2253d631d441ba7049a7fe
Differentiator: e8497ceab83c1b10ca233716d547b9af21b9344d
DropDown: 8a2116376c1981888557f72ec2ffc9a5e0e456ec
FDFullscreenPopGesture: a8a620179e3d9c40e8e00256dcee1c1a27c6d0f0
IceCream: 717d516a1c634eba8eaa8ce7d3d7bc5f7e40c2fa
IQKeyboardManagerSwift: 52962c76ab33532f15ad9f3ff4e5715eda5335bb
Kingfisher: 016c8b653a35add51dd34a3aba36b580041acc74
KVOController: d72ace34afea42468329623b3379ab3cd1d286b6
Material: a2a3f400a3b549d53ef89e56c58c4535b29db387
@ -135,6 +147,6 @@ SPEC CHECKSUMS:
SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e
PODFILE CHECKSUM: f2443e42f5062c17de4e7a770482b083d2659182
PODFILE CHECKSUM: 60eda42686b4893abd7ff19557301ef40fca0dff
COCOAPODS: 1.11.2
COCOAPODS: 1.11.3

146
README.en.md Normal file
View File

@ -0,0 +1,146 @@
# Bark
![Bark](https://raw.githubusercontent.com/Finb/Bark/master/Bark/Assets.xcassets/AppIcon.appiconset/Icon-60%403x.png)
[Bark](https://github.com/Finb/Bark) is an open-source push notifications app that respects your privacy. You can host your own server, or use the free public service, to send real-time alerts to your iPhone. Everything stays between you and your server (plus Apple). Bark supports webhooks and a simple HTTP API.
This repository contains the source code for the iOS app. For the backend, [bark-server](https://github.com/Finb/bark-server) is written in Go, and supports Docker and serverless functions.
## Send a Push Notification
### API v2
**Bark is switching to to API v2, which supports webhooks. See [API_V2.md](https://github.com/Finb/bark-server/blob/master/docs/API_V2.md).**
### API v1
The Bark app supports both API v1 and v2, but the UI currently only showcases v1. API v1 supports both HTTP GET or POST requests with the following format.
```
https://{server}/{key}/{body}
https://{server}/{key}/{title}/{body}
```
API v1 also supports optional query strings.
`automaticallyCopy` (boolean)
```
//Automatically copy the body text of a notification (iOS 14.4 and below)
https://api.day.app/key/body?automaticallyCopy=1
```
`badge` (integer)
```
//Specify the badge count on the app icon
https://api.day.app/key/body?badge=3
```
`copy` (string)
```
//Specify clipboard content when copying
https://api.day.app/key/body?copy=1234
```
`group` (string)
```
//Let iOS sort notifications by groups
https://api.day.app/key/body?group=groupName
```
`icon` (string)
```
//Display a custom icon on a notification (iOS 15 and above)
https://api.day.app/key/body?icon=http://day.app/assets/images/avatar.jpg
```
`isArchive` (boolean)
```
//Archive a notification if 1, prevent archiving if 0, default settings will apply if unset
https://api.day.app/key/body?isArchive=1
```
`level` (boolean)
```
//Set importance and delivery timing of a notification
//active (default): presents the notification immediately, lights up the screen, and can play a sound
//timeSensitive: similar to active, but can break through system controls such as Notification Summary and Focus
//passive: adds the notification to the notification list without lighting up the screen or playing a sound
https://api.day.app/key/body?level=timeSensitive
```
`sound` (string)
```
//Select an alert sound for a notification
//Available sounds are located in the 'Sounds' folder and can be previewed in the app
https://api.day.app/key/body?sound=alarm
```
`url` (string)
```
//Open an URL by clicking on a notification
https://api.day.app/key/body?automaticallyCopy=1
```
## FAQ
* Time Sensitive notifications dont work.
Try restarting your device.
* I cant archive or copy my notifications.
Try restarting your device. Sometimes Apples notification extension ([UNNotificationServiceExtension](https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension)) fails to run. Consequently Bark cant excute the code to do so.
* The auto copy function doesnt work.
Since iOS 14.5, Apple has tightened clipboard security, and copying is no longer possible without user intervention. You can pull down, long press or open the notification to trigger the auto copy function, or use the system popup button to copy.
* Are there any limits on the number of notifications I can receive?
There arent any limitations. However, if you make a massive amount (> 100,000) of bad requests (e.g., 500 or 404 resulted from bad formatting), your IP will be banned.
* How do I open the Message History tab on startup?
Bark will open on the same tab it was closed on. Close Bark on the Message History tab.
* Does Bark support POST requests?
Bark supports GET and POST requests with the same set of parameters. If you want to use webhooks with JSON, you must use API v2. See [API_V2.md](https://github.com/Finb/bark-server/blob/master/docs/API_V2.md).
* My notifications failed to push/got messed up when there are special characters.
You need to use URL encoding to convert the special chracters, or the Bark server might not be able to properly interprete the request. Production-level libraries usually handle special characters automatically.
* How can I make sure my notifications stay private?
This is how your notification reaches you with Bark:
Sender → Bark Server → Apple Push Notification Service → Your iPhone → Bark Client
The Bark server and client are the two attack surfaces that we control. You can mitigate the risks by:
1. Deploy your own Bark server with [the open-source backend](https://github.com/Finb/bark-server).
2. Make sure the Bark app has not been tampered.
Bark uses GitHub Actions to build and publish itself to the App Store. You can verify whether the run_id in the app matches the one on GitHub to ensure the app was built from its original codebase.
You can also find build configurations, version and build numbers in the run log. The version and build numbers must be unique to be accepted by the App Store. Therefore you can verify whether the version and build numbers of your app match the ones in the run log.
## Community Ports
Push from Chrome Extension: [Bark-Chrome-Extension](https://github.com/xlvecle/Bark-Chrome-Extension)
Push from Web with scheduled tasks: [Bark Assisatant](https://api.ihint.me/bark.html)
Push from Windows: [BarkHelper](https://github.com/HsuDan/BarkHelper)
Push from CLI: [bark-cli](https://github.com/JasonkayZK/bark-cli)
Quicker Action: https://getquicker.net/Sharedaction?code=e927d844-d212-4428-758d-08d69de12a3b
Bark for Wox: [Wox.Plugin.Bark](https://github.com/Zeroto521/Wox.Plugin.Bark)
java-bark-server: [https://gitee.com/hotlcc/java-bark-server](https://gitee.com/hotlcc/java-bark-server)
Python for Bark: [barknotificator
](https://github.com/funny-cat-happy/barknotificator)

View File

@ -1,5 +1,5 @@
## 常见问题
[https://day.app/2021/06/barkfaq/](https://day.app/2021/06/barkfaq/)
## 使用文档
[https://bark.day.app](https://bark.day.app)
## 问题反馈 Telegram 群
[Bark反馈群](https://t.me/joinchat/OsCbLzovUAE0YjY1)
@ -103,26 +103,8 @@ https://api.day.app/yourkey/时效性通知?level=timeSensitive
## 跨平台的命令行应用
[https://github.com/JasonkayZK/bark-cli](https://github.com/JasonkayZK/bark-cli)
## bark-jssdk
> 几行代码,用 nodejs 使用 bark sdk
[https://github.com/afeiship/bark-jssdk](https://github.com/afeiship/bark-jssdk)
```js
import BarkJssdk from '@jswork/bark-jssdk';
// use instance
const sdk = new BarkJssdk({ sdkKey: 'YOUR_SDK_KEY'});
// notify
sdk.notify({ title: 'Tips', body: 'New message comming.' });
// OR use BarkJssdk.notify directly
BarkJssdk.notify({
sdkKey: 'YOUR_SDK_KEY',
title: 'Tips',
body: 'New message comming.'
});
```
## GitHub Actions
[https://github.com/harryzcy/action-bark](https://github.com/harryzcy/action-bark)
## Quicker 动作
使用 Quicker 软件在 Windows 上将选中文字一键推送到iPhone支持打开URL和自动复制推送内容
@ -130,3 +112,12 @@ BarkJssdk.notify({
## Bark for Wox
[https://github.com/Zeroto521/Wox.Plugin.Bark](https://github.com/Zeroto521/Wox.Plugin.Bark)
## bark-jssdk
[https://github.com/afeiship/bark-jssdk](https://github.com/afeiship/bark-jssdk)
## java-bark-server
[https://gitee.com/hotlcc/java-bark-server](https://gitee.com/hotlcc/java-bark-server)
## Python for Bark
[barknotificator](https://github.com/funny-cat-happy/barknotificator)

View File

@ -10,8 +10,8 @@ import Foundation
import RxCocoa
class ArchiveSettingCellViewModel: ViewModel {
var on: BehaviorRelay<Bool>
init(on: Bool) {
self.on = BehaviorRelay<Bool>(value: on)
init(on: BehaviorRelay<Bool>) {
self.on = on
super.init()
}
}

48
View/BKDropDownCell.swift Normal file
View File

@ -0,0 +1,48 @@
//
// BKDropDownCellTableViewCell.swift
// Bark
//
// Created by huangfeng on 2023/2/9.
// Copyright © 2023 Fin. All rights reserved.
//
import DropDown
import UIKit
class BKDropDownCell: DropDownCell {
@IBOutlet var selectBackgroundView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = BKColor.white
self.selectBackgroundView.layer.cornerRadius = 10
self.selectBackgroundView.clipsToBounds = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
let executeSelection: () -> Void = { [weak self] in
guard let `self` = self else { return }
let selectedBackgroundColor = BKColor.grey.lighten5
if selected {
self.selectBackgroundView.backgroundColor = selectedBackgroundColor
self.optionLabel.textColor = BKColor.grey.darken4
} else {
self.selectBackgroundView.backgroundColor = .clear
self.optionLabel.textColor = BKColor.grey.darken3
}
}
if animated {
UIView.animate(withDuration: 0.3, animations: {
executeSelection()
})
} else {
executeSelection()
}
accessibilityTraits = selected ? .selected : .none
}
}

56
View/BKDropDownCell.xib Normal file
View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="62" id="Ov8-GI-Rat" customClass="BKDropDownCell" customModule="Bark" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="393" height="62"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Ov8-GI-Rat" id="JkW-au-90U">
<rect key="frame" x="0.0" y="0.0" width="393" height="62"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Be1-Hg-cfz" userLabel="Select Background View">
<rect key="frame" x="6" y="4" width="381" height="54"/>
<viewLayoutGuide key="safeArea" id="GJ2-4o-aoF"/>
<color key="backgroundColor" systemColor="linkColor"/>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2lm-va-4a1">
<rect key="frame" x="16" y="5" width="361" height="52"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="2lm-va-4a1" secondAttribute="bottom" constant="5" id="A61-Or-MrS"/>
<constraint firstItem="Be1-Hg-cfz" firstAttribute="leading" secondItem="JkW-au-90U" secondAttribute="leading" constant="6" id="Cl3-sY-bfR"/>
<constraint firstAttribute="trailing" secondItem="Be1-Hg-cfz" secondAttribute="trailing" constant="6" id="M5z-T7-Vbz"/>
<constraint firstItem="Be1-Hg-cfz" firstAttribute="top" secondItem="JkW-au-90U" secondAttribute="top" constant="4" id="YzQ-6y-cJa"/>
<constraint firstItem="2lm-va-4a1" firstAttribute="top" secondItem="JkW-au-90U" secondAttribute="top" constant="5" id="cix-Ub-eyW"/>
<constraint firstAttribute="trailing" secondItem="2lm-va-4a1" secondAttribute="trailing" constant="16" id="fmp-HQ-Icr"/>
<constraint firstItem="2lm-va-4a1" firstAttribute="leading" secondItem="JkW-au-90U" secondAttribute="leading" constant="16" id="kdu-uI-TlX"/>
<constraint firstAttribute="bottom" secondItem="Be1-Hg-cfz" secondAttribute="bottom" constant="4" id="twp-ah-AEA"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="optionLabel" destination="2lm-va-4a1" id="try-1f-LgE"/>
<outlet property="selectBackgroundView" destination="Be1-Hg-cfz" id="mpw-MN-DAU"/>
</connections>
<point key="canvasLocation" x="46.564885496183201" y="-64.08450704225352"/>
</tableViewCell>
</objects>
<resources>
<systemColor name="linkColor">
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

101
View/BorderTextField.swift Normal file
View File

@ -0,0 +1,101 @@
//
// BorderTextField.swift
// Bark
//
// Created by huangfeng on 2023/2/6.
// Copyright © 2023 Fin. All rights reserved.
//
import UIKit
class InsetTextField: UITextField {
var insets = UIEdgeInsets.zero
override func textRect(forBounds bounds: CGRect) -> CGRect {
let bounds = super.textRect(forBounds: bounds)
return bounds.inset(by: insets)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
let bounds = super.textRect(forBounds: bounds)
return bounds.inset(by: insets)
}
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
let bounds = super.textRect(forBounds: bounds)
return bounds.inset(by: insets)
}
}
class BorderTextField: InsetTextField {
var isSelecting: Bool = true {
didSet {
UIView.animate(withDuration: 0.3) {
if self.isSelecting {
self.backgroundView.borderColor = BKColor.blue.darken5
self.backgroundView.shadowColor = BKColor.blue.darken5
self.backgroundView.layer.shadowOpacity = 0.3
}
else {
self.backgroundView.borderColor = BKColor.grey.lighten2
self.backgroundView.shadowColor = BKColor.grey.lighten2
self.backgroundView.layer.shadowOpacity = 0
}
}
}
}
let backgroundView: UIView = {
let view = UIView()
view.backgroundColor = BKColor.white
view.isUserInteractionEnabled = false
view.cornerRadiusPreset = .cornerRadius3
view.shadowColor = BKColor.grey.lighten2
view.layer.shadowOffset = CGSize(width: 0, height: 0)
view.layer.shadowRadius = 2
view.layer.shadowOpacity = 0
view.borderColor = BKColor.grey.lighten2
view.borderWidthPreset = .border2
return view
}()
init(title: String? = nil) {
super.init(frame: CGRect.zero)
self.insets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
self.textColor = BKColor.grey.darken3
self.font = UIFont.systemFont(ofSize: 14)
self.textAlignment = .left
self.insertSubview(backgroundView, at: 0)
backgroundView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
self.delegate = self
}
override var placeholder: String? {
didSet{
self.attributedPlaceholder = NSAttributedString(string: placeholder ?? "" , attributes: [
.font: self.font ?? UIFont.systemFont(ofSize: 14),
.foregroundColor: BKColor.grey.darken1
])
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension BorderTextField: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
self.isSelecting = true
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.isSelecting = false
}
}

137
View/DropBoxView.swift Normal file
View File

@ -0,0 +1,137 @@
//
// DropBoxView.swift
// Bark
//
// Created by huangfeng on 2023/2/2.
// Copyright © 2023 Fin. All rights reserved.
//
import DropDown
import UIKit
import RxCocoa
import RxSwift
class DropBoxView: UIView {
let valueLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.textColor = BKColor.grey.darken3
return label
}()
let dropIconView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "baseline_keyboard_arrow_down_black_24pt")?.withRenderingMode(.alwaysTemplate)
imageView.tintColor = BKColor.grey.lighten2
return imageView
}()
var isSelecting: Bool = true {
didSet {
UIView.animate(withDuration: 0.3) {
if self.isSelecting {
self.borderColor = BKColor.blue.darken5
self.shadowColor = BKColor.blue.darken5
self.layer.shadowOpacity = 0.3
}
else {
self.borderColor = BKColor.grey.lighten2
self.shadowColor = BKColor.grey.lighten2
self.layer.shadowOpacity = 0
}
}
}
}
var values: [String] {
didSet {
self.currentValue = values.first
}
}
var currentValue: String? {
didSet {
self.valueLabel.text = currentValue
self.currentValueChanged?(self.currentValue)
}
}
var currentValueChanged: ((String?) -> Void)?
init(values: [String]) {
self.values = values
super.init(frame: CGRect.zero)
self.backgroundColor = BKColor.white
self.borderColor = BKColor.grey.lighten2
self.borderWidthPreset = .border2
self.cornerRadiusPreset = .cornerRadius3
self.shadowColor = BKColor.grey.lighten2
self.layer.shadowOffset = CGSize(width: 0, height: 0)
self.layer.shadowRadius = 2
self.layer.shadowOpacity = 0
addSubview(valueLabel)
addSubview(dropIconView)
self.dropIconView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.right.equalTo(-6)
}
self.valueLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalTo(16)
}
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap)))
defer {
self.currentValue = self.values.first
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func tap() {
let dropDown = DropDown(anchorView: self)
dropDown.cellNib = UINib(nibName: "BKDropDownCell", bundle: Bundle(for: BKDropDownCell.self))
dropDown.cellHeight = 50
dropDown.cornerRadius = 10
dropDown.clipsToBounds = true
dropDown.bottomOffset = CGPoint(x: 0, y: 50)
dropDown.dataSource = self.values
dropDown.selectionAction = { [weak self] _, str in
self?.currentValue = str
self?.isSelecting = false
}
dropDown.cancelAction = { [weak self] in
self?.isSelecting = false
}
self.isSelecting = true
dropDown.show()
}
}
extension Reactive where Base: DropBoxView {
var currentValueChanged: ControlEvent<String?> {
let source = Observable<String?>.create { [weak control = self.base] observer -> Disposable in
MainScheduler.ensureExecutingOnScheduler()
guard let control = control else {
observer.onCompleted()
return Disposables.create()
}
control.currentValueChanged = { value in
observer.onNext(value)
}
return Disposables.create()
}
return ControlEvent(events: source)
}
}

View File

@ -0,0 +1,41 @@
//
// GesturePassTextView.swift
// Bark
//
// Created by huangfeng on 2023/7/26.
// Copyright © 2023 Fin. All rights reserved.
//
import UIKit
class GesturePassTextView: UITextView {
var superCell: UITableViewCell? = nil
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let cell = superCell {
cell.touchesBegan(touches, with: event)
return
}
super.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let cell = superCell {
cell.touchesMoved(touches, with: event)
return
}
super.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let cell = superCell {
cell.touchesEnded(touches, with: event)
return
}
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
if let cell = superCell {
cell.touchesCancelled(touches, with: event)
return
}
super.touchesCancelled(touches, with: event)
}
}

62
View/GradientButton.swift Normal file
View File

@ -0,0 +1,62 @@
//
// UIView+Gradient.swift
// Bark
//
// Created by huangfeng on 2023/2/10.
// Copyright © 2023 Fin. All rights reserved.
//
import UIKit
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
enum GradientOrientation {
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint: CGPoint {
return points.startPoint
}
var endPoint: CGPoint {
return points.endPoint
}
var points: GradientPoints {
switch self {
case .topRightBottomLeft:
return (CGPoint(x: 0.0, y: 1.0), CGPoint(x: 1.0, y: 0.0))
case .topLeftBottomRight:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 1, y: 1))
case .horizontal:
return (CGPoint(x: 0.0, y: 0.5), CGPoint(x: 1.0, y: 0.5))
case .vertical:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 0.0, y: 1.0))
}
}
}
class GradientButton: UIButton {
lazy var gradient: CAGradientLayer = {
let gradient = CAGradientLayer()
return gradient
}()
func applyGradient(withColours colours: [UIColor], gradientOrientation orientation: GradientOrientation) {
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
if gradient.superlayer == nil {
self.layer.insertSublayer(gradient, at: 0)
}
}
override func layoutSubviews() {
super.layoutSubviews()
gradient.frame = self.bounds
}
}

69
View/HUD.swift Normal file
View File

@ -0,0 +1,69 @@
//
// HUD.swift
// Bark
//
// Created by huangfeng on 2023/3/6.
// Copyright © 2023 Fin. All rights reserved.
//
import SVProgressHUD
import UIKit
class BarkProgressHUD: SVProgressHUD {
override class func displayDuration(for string: String?) -> TimeInterval {
return min(Double((string ?? "").utf8.count) * 0.06 + 0.5, 5.0)
}
}
open class ProgressHUD: NSObject {
open class func show() {
BarkProgressHUD.show()
}
open class func showWithClearMask() {
BarkProgressHUD.show()
}
open class func dismiss() {
BarkProgressHUD.dismiss()
}
open class func showWithStatus(_ status: String!) {
BarkProgressHUD.show(withStatus: status)
}
open class func success(_ status: String!) {
BarkProgressHUD.showSuccess(withStatus: status)
}
open class func error(_ status: String!) {
BarkProgressHUD.showError(withStatus: status)
}
open class func inform(_ status: String!) {
BarkProgressHUD.showInfo(withStatus: status)
}
}
public func HUDSuccess(_ status: String?) {
ProgressHUD.success(status ?? "")
}
public func HUDError(_ status: String?) {
ProgressHUD.error(status ?? "")
}
public func HUDInform(_ status: String?) {
ProgressHUD.inform(status ?? "")
}
public func HUDShow() {
ProgressHUD.show()
}
public func HUDShowWithStatus(_ status: String!) {
ProgressHUD.showWithStatus(status)
}
public func HUDDismiss() {
ProgressHUD.dismiss()
}

View File

@ -7,7 +7,9 @@
//
import Material
import RxSwift
import UIKit
class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
let backgroundPanel: UIView = {
let view = UIView()
@ -17,29 +19,15 @@ class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
return view
}()
let titleLabel: UILabel = {
let label = UILabel()
label.font = RobotoFont.medium(with: 16)
label.textColor = BKColor.grey.darken4
label.numberOfLines = 0
return label
}()
let bodyLabel: UILabel = {
let label = UILabel()
let bodyLabel: UITextView = {
let label = UITextView()
label.isEditable = false
label.dataDetectorTypes = [.phoneNumber, .link]
label.isScrollEnabled = false
label.textContainerInset = .zero
label.textContainer.lineFragmentPadding = 0
label.font = RobotoFont.regular(with: 14)
label.textColor = BKColor.grey.darken4
label.numberOfLines = 0
return label
}()
let urlLabel: UILabel = {
let label = BKLabel()
label.hitTestSlop = UIEdgeInsets(top: -20, left: -20, bottom: -20, right: -20)
label.isUserInteractionEnabled = true
label.font = RobotoFont.regular(with: 14)
label.textColor = BKColor.blue.darken1
label.numberOfLines = 0
return label
}()
@ -50,12 +38,6 @@ class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
return label
}()
let bodyStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
return stackView
}()
let separatorLine: UIImageView = {
let imageView = UIImageView()
imageView.backgroundColor = BKColor.background.primary
@ -68,44 +50,55 @@ class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
self.backgroundColor = BKColor.background.primary
contentView.addSubview(backgroundPanel)
contentView.addSubview(bodyStackView)
bodyStackView.addArrangedSubview(titleLabel)
bodyStackView.addArrangedSubview(bodyLabel)
bodyStackView.addArrangedSubview(urlLabel)
bodyStackView.spacing = 6
bodyStackView.setCustomSpacing(12, after: bodyLabel)
contentView.addSubview(bodyLabel)
contentView.addSubview(dateLabel)
contentView.addSubview(separatorLine)
self.urlLabel.addGestureRecognizer(UITapGestureRecognizer())
layoutView()
}
layoutView()
let tap = UITapGestureRecognizer(target: self, action: #selector(tap))
tap.name = "messageTap"
tap.delegate = self
bodyLabel.addGestureRecognizer(tap)
}
@objc func tap() {
var view = self.superview
while view != nil, (view as? UITableView) == nil {
view = view?.superview
}
guard let tableView = view as? UITableView else {
return
}
guard let indexPath = tableView.indexPath(for: self) else {
return
}
tableView.delegate?.tableView?(tableView, didSelectRowAt: indexPath)
}
// UITableView didSelectRow
override func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.name == "messageTap", otherGestureRecognizer.name == "UITextInteractionNameLinkTap" {
return true
}
return false
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func layoutView() {
bodyStackView.snp.makeConstraints { make in
make.left.top.equalToSuperview().offset(16)
make.right.equalToSuperview().offset(-16)
}
titleLabel.snp.remakeConstraints { make in
make.left.equalTo(12)
make.right.equalTo(-12)
}
bodyLabel.snp.remakeConstraints { make in
make.left.right.equalTo(titleLabel)
}
urlLabel.snp.makeConstraints { make in
make.left.right.equalTo(bodyLabel)
make.top.equalTo(16)
make.left.equalTo(28)
make.right.equalTo(-28)
}
dateLabel.snp.remakeConstraints { make in
make.left.equalTo(bodyLabel)
make.top.equalTo(bodyStackView.snp.bottom).offset(12)
make.top.equalTo(bodyLabel.snp.bottom).offset(12)
}
separatorLine.snp.remakeConstraints { make in
make.left.right.bottom.equalToSuperview()
@ -124,16 +117,42 @@ class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
override func bindViewModel(model: MessageTableViewCellViewModel) {
super.bindViewModel(model: model)
model.title.bind(to: self.titleLabel.rx.text).disposed(by: rx.reuseBag)
model.body.bind(to: self.bodyLabel.rx.text).disposed(by: rx.reuseBag)
model.url.bind(to: self.urlLabel.rx.text).disposed(by: rx.reuseBag)
Observable.combineLatest(model.title, model.body, model.url).subscribe { title, body, url in
let text = NSMutableAttributedString(
string: body,
attributes: [.font: RobotoFont.regular(with: 14), .foregroundColor: BKColor.grey.darken4]
)
if title.count > 0 {
// spacer
text.insert(NSAttributedString(
string: "\n",
attributes: [.font: RobotoFont.medium(with: 6)]
), at: 0)
text.insert(NSAttributedString(
string: title + "\n",
attributes: [.font: RobotoFont.medium(with: 16), .foregroundColor: BKColor.grey.darken4]
), at: 0)
}
if url.count > 0 {
// spacer
text.append(NSAttributedString(
string: "\n ",
attributes: [.font: RobotoFont.medium(with: 8)]
))
text.append(NSAttributedString(string: "\n\(url)", attributes: [
.font: RobotoFont.regular(with: 14),
.foregroundColor: BKColor.grey.darken4,
.link: url
]))
}
self.bodyLabel.attributedText = text
}.disposed(by: rx.disposeBag)
model.date.bind(to: self.dateLabel.rx.text).disposed(by: rx.reuseBag)
model.title.map { $0.count <= 0 }.bind(to: self.titleLabel.rx.isHidden).disposed(by: rx.reuseBag)
model.url.map { $0.count <= 0 }.bind(to: self.urlLabel.rx.isHidden).disposed(by: rx.reuseBag)
self.urlLabel.gestureRecognizers?.first?.rx.event
.map { [weak self] _ in self?.urlLabel.text ?? "" }
.bind(to: model.urlTap).disposed(by: rx.reuseBag)
}
}

View File

@ -19,7 +19,6 @@ class MessageTableViewCellViewModel: ViewModel {
let url: BehaviorRelay<String>
let date: BehaviorRelay<String>
let urlTap: PublishRelay<String>
init(message: Message) {
self.message = message
@ -28,8 +27,7 @@ class MessageTableViewCellViewModel: ViewModel {
self.body = BehaviorRelay<String>(value: message.body ?? "")
self.url = BehaviorRelay<String>(value: message.url ?? "")
self.date = BehaviorRelay<String>(value: (message.createDate ?? Date()).agoFormatString())
self.urlTap = PublishRelay<String>()
super.init()
}
}

View File

@ -16,6 +16,7 @@ class iCloudStatusCell: UITableViewCell {
self.backgroundColor = BKColor.background.secondary
self.textLabel?.text = NSLocalizedString("iCloudSatatus")
self.detailTextLabel?.text = ""
self.detailTextLabel?.textColor = BKColor.grey.darken2
CKContainer.default().accountStatus { status, _ in
dispatch_sync_safely_main_queue {
switch status {

0
docs/.nojekyll Normal file
View File

1
docs/CNAME Normal file
View File

@ -0,0 +1 @@
bark.day.app

34
docs/README.md Normal file
View File

@ -0,0 +1,34 @@
### Bark <!-- {docsify-ignore-all} -->
- 免费、轻量简单调用接口即可给自己的iPhone发送推送。
- 依赖苹果APNs及时、稳定、可靠
- 不会消耗设备的电量, 基于系统推送服务与推送扩展APP本体并不需要运行。
- 隐私安全,可以通过一些方式确保包含作者本人在内的所有人都无法窃取你的隐私。<br>*点击详细了解如何保障[隐私安全](/privacy)*
### 源码
- [Bark](https://github.com/Finb/Bark) 是完整开源的 iOS APP用来接收自定义推送。
- [bark-server](https://github.com/Finb/bark-server) 是完整开源的 Bark 服务后端用来接收用户的推送请求并转发给苹果APNS。
### 反馈
- [Bark 问题反馈群](https://t.me/joinchat/OsCbLzovUAE0YjY1) (注意点击入群验证)
- [GitHub Issues](https://github.com/Finb/Bark/issues)
### 免费
Bark **2018年7月**上线,至少会维持运营到 **2031年7月** 。*(说不出口“永久”这个词,后续还有需求再续吧)*<br>
APP在维持期间不会有任何形式的收费与广告各位彦祖放心使用。
### 赞助
目前仅接收 GitHub 赞助,同时非常感谢每一位赞助者<br>
赞助者:[https://github.com/sponsors/Finb](https://github.com/sponsors/Finb)
### 文档
- **App**
- [使用教程](/tutorial)
- [推送加密](/encryption)
- [常见问题](/faq)
- **服务端**
- [部署服务](/deploy)
- [直接推送](/apns)
- [编译代码](/build)
- [推送证书](/cert)
- [隐私安全](/privacy)

11
docs/_coverpage.md Executable file
View File

@ -0,0 +1,11 @@
![logo](_media/Icon.png)
# Bark <small></small>
> 一款注重隐私、安全可控的自定义通知推送工具。
- 免费、简单、安全
- 打开即用
[GitHub](https://github.com/finb/bark)
[Get Started](#bark)

BIN
docs/_media/Icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
docs/_media/environment.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

BIN
docs/_media/example.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

5
docs/_navbar.md Normal file
View File

@ -0,0 +1,5 @@
* Translations
- [:cn: 简体中文](/)
- [:uk: English](/en-us/)
- [:tr: Türkçe](/tr/)

11
docs/_sidebar.md Normal file
View File

@ -0,0 +1,11 @@
- [Bark](/#bark)
- **App**
- [使用教程](/tutorial)
- [推送加密](/encryption)
- [常见问题](/faq)
- **服务端**
- [部署服务](/deploy)
- [直接推送](/apns)
- [编译代码](/build)
- [推送证书](/cert)
- [隐私安全](/privacy)

55
docs/apns.md Normal file
View File

@ -0,0 +1,55 @@
### 直接调用APNS接口
如果有设备的 DeviceToken可在APP中查看就可以调用苹果APNS接口直接给设备发推送APP中也无需添加服务器。<br>
以下是命令行发推送示例:
```shell
# 设置环境变量
# 下载 key https://raw.githubusercontent.com/Finb/bark-server/master/deploy/AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8
# 将 key 文件路径填到下面
TOKEN_KEY_FILE_NAME=
# 从 app 设置中复制 DeviceToken 到这
DEVICE_TOKEN=
#下面的不要修改
TEAM_ID=5U8LBRXG3A
AUTH_KEY_ID=LH4T9V5U4R
TOPIC=me.fin.bark
APNS_HOST_NAME=api.push.apple.com
# 生成TOKEN
JWT_ISSUE_TIME=$(date +%s)
JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}"
JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
# 如果有条件,最好改进脚本缓存此 Token。Token 30分钟内复用同一个每过30分钟重新生成
# 苹果文档指明 TOKEN 生成间隔最短20分钟TOKEN 有效期最长60分钟
# 间隔过短重复生成会生成失败TOKEN 超过1小时不重新生成就不能推送
# 但经我不负责任的简单测试可以短时间内正常生成
# 此处仅提醒,或许可能因频繁生成 TOKEN 导致推送失败
AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"
#发送推送
curl -v --header "apns-topic: $TOPIC" --header "apns-push-type: alert" --header "authorization: bearer $AUTHENTICATION_TOKEN" --data '{"aps":{"alert":"test"}}' --http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}
```
### 推送参数格式
参考 https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification<br>
一定要带上 "mutable-content" : 1 ,否则推送扩展不执行,不会保存推送。<br>
示例:
```js
{
"aps": {
"mutable-content": 1,
"alert": {
"title" : "title",
"body": "body"
},
"category": "myNotificationCategory",
"sound": "minuet.caf"
},
"icon": "https://day.app/assets/images/avatar.jpg"
}
```

47
docs/build.md Normal file
View File

@ -0,0 +1,47 @@
## 下载源码
从GitHub下载源码 [bark-server](https://github.com/Finb/bark-server)
```sh
git clone https://github.com/Finb/bark-server.git
```
## 配置依赖
- Golang 1.18+
- Go Mod (env GO111MODULE=on)
- Go Mod Proxy (env GOPROXY=https://goproxy.cn)
- 安装 [go-task](https://taskfile.dev/installation/)
## 交叉编译所有平台
```sh
task
```
## 编译指定平台
```sh
task linux_amd64
task linux_amd64_v3
```
## 支持的平台
- linux_386
- linux_amd64
- linux_amd64_v2
- linux_amd64_v3
- linux_amd64_v4
- linux_armv5
- linux_armv6
- linux_armv7
- linux_armv8
- linux_mips_hardfloat
- linux_mipsle_softfloat
- linux_mipsle_hardfloat
- linux_mips64
- linux_mips64le
- windows_386.exe
- windows_amd64.exe
- windows_amd64_v2.exe
- windows_amd64_v3.exe
- windows_amd64_v4.exe
- darwin_amd64
- darwin_arm64

6
docs/cert.md Normal file
View File

@ -0,0 +1,6 @@
当你需要集成Bark到自己的系统或重新实现后端代码时可能需要推送证书
##### 有效期到: *永久*
##### Key ID*LH4T9V5U4R*
##### TeamID*5U8LBRXG3A*
##### 下载地址:[AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8](https://github.com/Finb/bark-server/releases/download/v1.0.2/AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8)

86
docs/deploy.md Normal file
View File

@ -0,0 +1,86 @@
## Docker
```
docker run -dt --name bark -p 8080:8080 -v `pwd`/bark-data:/data finab/bark-server
```
## Docker-Compose
```
mkdir bark && cd bark
curl -sL https://git.io/JvSRl > docker-compose.yaml
docker-compose up -d
```
## 手动部署
1. 根据平台下载可执行文件:<br> <a href='https://github.com/Finb/bark-server/releases'>https://github.com/Finb/bark-server/releases</a><br>
或自己编译<br>
<a href="https://github.com/Finb/bark-server">https://github.com/Finb/bark-server</a>
2. 运行
```
./bark-server_linux_amd64 -addr 0.0.0.0:8080 -data ./bark-data
```
3. 你可能需要
```
chmod +x bark-server_linux_amd64
```
请注意 bark-server 默认使用 /data 目录保存数据,请确保 bark-server 有权限读写 /data 目录,或者你可以使用 `-data` 选项指定一个目录
## Serverless
默认提供 Heroku ~~免费~~ 一键部署 (2022-11-28日后收费)<br>
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/finb/bark-server)<br>
其他支持WEB路由的 serverless 服务器可以使用 `bark-server -serverless true` 开启。
开启后, bark-server 会读取系统环境变量 BARK_KEY 和 BARK_DEVICE_TOKEN, 需提前设置好。
| 变量名 | 填写要求 |
| ---- | ---- |
| BARK_KEY | 除了不能填 "push" 外,可以随便填写你喜欢的。|
| BARK_DEVICE_TOKEN | Bark App 设置中显示的 DeviceToken此 Token 是 APNS 真实设备 Token ,请不要泄露 |
请注意 Serverless 模式只允许一台设备使用
## Render
Render 能非常简单的创建免费的 bark-server
1. [注册](https://dashboard.render.com/register/)一个 Render 账号
2. 创建一个 [New Web Service](https://dashboard.render.com/select-repo?type=web)
3. 在底部的 **Public Git repository** 输入框输入下面的URL
```
https://github.com/Finb/bark-server
```
4. 点击 **Continue** 输入表单
* Name - 名称,随便取个名字,例如 bark-server
* Region - 服务器地区,选择离你近的
* Start Command - 程序执行命令,填`./app -serverless true`。(注意不要漏了 ./app 前面的点)
* Instance Type - 选 Free ,免费的足够用了。
* 点击 Advanced 展开更多选项
* 点击 Add Environment Variable 添加 Serverless 模式需要的 BARK_KEY 和 BARK_DEVICE_TOKEN 字段。 (填写要求参考 [Serverless](#Serverless)) <br><img src="../_media/environment.png" />
* 其他的默认不动
5. 点击底部的 Create Web Service 按钮,然后等待状态从 In progress 变成 Live可能需要几分钟到十几分钟。
6. 页面顶部找到你的服务器URL这个就是bark-server服务器URL在 Bark App 中添加即可
```
https://[your-server-name].onrender.com
```
7. 如果添加失败,可以等待一段时间再试,有可能服务还没准备好。
8. 不添加到 Bark App 中也可以直接调用就能发推送。BARK_KEY 就是上面环境变量中你填写的。
```
https://[your-server-name].onrender.com/BARK_KEY/推送内容
```
## 测试
```
curl http://0.0.0.0:8080/ping
```
返回 pong 就证明部署成功了
## 其他
1. APP端负责将<a href="https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application">DeviceToken</a>发送到服务端。 <br>服务端收到一个推送请求后将发送推送给Apple服务器。然后手机收到推送
2. 服务端代码: <a href='https://github.com/Finb/bark-server'>https://github.com/Finb/bark-server</a><br>
3. App代码: <a href="https://github.com/Finb/Bark">https://github.com/Finb/Bark</a>

34
docs/en-us/README.md Normal file
View File

@ -0,0 +1,34 @@
### Bark <!-- {docsify-ignore-all} -->
- Free, lightweight! Simply call the interface to send push notifications to your own iPhone.
- Depends on Apple APNs, timely, stable and reliable
- Does not consume devices battery power. Based on system push service and push extension, APP itself does not need to run.
- Privacy and security. You can ensure that no one, including the author himself, can steal your privacy through some ways.<br>*Click for details on how to ensure [privacy and security](/en-us/privacy)*
### Source code
- [Bark](https://github.com/Finb/Bark) is a fully open source iOS APP for receiving custom push notifications
- [bark-server](https://github.com/Finb/bark-server) is a fully open source Bark service backend for receiving user push requests and forwarding them to Apple APNS.
### Feedback
- [Telegram Group](https://t.me/joinchat/OsCbLzovUAE0YjY1) Note: click verification to join group
- [GitHub Issues](https://github.com/Finb/Bark/issues)
### Free
Bark was launched in **July 2018** and will maintain operation until at least **July 2031**. *(Cant say the word “permanent”, lets renew it later if there is still demand)*<br>
APP will not have any form of charge or advertisement during maintenance period. Feel free to use it.
### Sponsorship
Currently only accept GitHub sponsorship. Thank you very much for every sponsor<br>
Sponsors[https://github.com/sponsors/Finb](https://github.com/sponsors/Finb)
### Documentation
- **App**
- [Tutorial](/en-us/tutorial)
- [Encryption](/en-us/encryption)
- [FAQs](/en-us/faq)
- **Server**
- [Deploy](/en-us/deploy)
- [Direct Push](/en-us/apns)
- [Build](/en-us/build)
- [Certificate](/en-us/cert)
- [Privacy](/en-us/privacy)

11
docs/en-us/_coverpage.md Executable file
View File

@ -0,0 +1,11 @@
![logo](../_media/Icon.png)
# Bark <small></small>
> A privacy-focused, secure and controllable custom notification push tool.
- Free, simple and safe
- Ready to use
[GitHub](https://github.com/finb/bark)
[Get Started](#bark)

11
docs/en-us/_sidebar.md Normal file
View File

@ -0,0 +1,11 @@
- [Bark](/en-us/#bark)
- **App**
- [Tutorial](/en-us/tutorial)
- [Encryption](/en-us/encryption)
- [FAQs](/en-us/faq)
- **Server**
- [Deploy](/en-us/deploy)
- [Direct Push](/en-us/apns)
- [Build](/en-us/build)
- [Certificate](/en-us/cert)
- [Privacy](/en-us/privacy)

1
docs/en-us/apns.md Normal file
View File

@ -0,0 +1 @@
...

1
docs/en-us/build.md Normal file
View File

@ -0,0 +1 @@
...

1
docs/en-us/cert.md Normal file
View File

@ -0,0 +1 @@
...

1
docs/en-us/deploy.md Normal file
View File

@ -0,0 +1 @@
...

36
docs/en-us/encryption.md Normal file
View File

@ -0,0 +1,36 @@
#### What is push encryption
Push encryption is a method to protect the push content, which uses a custom key to encrypt and decrypt the push content when sending and receiving. In this way, the push content will not be obtained or leaked by Bark server and Apple APNs server during transmission.
#### Set custom key
1. Open APP homepage
2. Find “Push Encryption”, click Encryption Settings
3. Select encryption algorithm, fill in KEY as required, click Done to save custom key
#### Send encrypted push
To send an encrypted push, you need to first convert the Bark request parameters into a json format string, then use the previously set key and corresponding algorithm to encrypt the string, and finally send the encrypted ciphertext as ciphertext parameter to the server.<br><br>
**For example**
```sh
#!/usr/bin/env bash
set -e
# bark key
deviceKey='F5u42Bd3HyW8KxkUqo2gRA'
# push payload
json='{"body": "test", "sound": "birdsong"}'
# must be 16 bit long
key='1234567890123456'
iv='1111111111111111'
# openssl requires Hex encoding of manual keys and IVs, not ASCII encoding.
key=$(printf $key | xxd -ps -c 200)
iv=$(printf $iv | xxd -ps -c 200)
ciphertext=$(echo -n $json | openssl enc -aes-128-cbc -K $key -iv $iv | base64)
# The console will print "d3QhjQjP5majvNt5CjsvFWwqqj2gKl96RFj5OO+u6ynTt7lkyigDYNA3abnnCLpr"
echo $ciphertext
curl --data-urlencode "ciphertext=$ciphertext" http://api.day.app/$deviceKey
```

46
docs/en-us/faq.md Normal file
View File

@ -0,0 +1,46 @@
#### Push usage limit <!-- {docsify-ignore-all} -->
Normal requests (HTTP status code 200) have no limit.<br>
But if more than 1000 error requests (HTTP status code 400 404 500) are made within 5 minutes, <b>the IP will be BAN for 24 hours</b>
#### Time-sensitive notifications not work
You can try to <b>restart your device</b> to solve it.
#### Unable to save notification history, or unable to copy by pulling down push or swiping left on lock screen without clicking copy button
You can try to <b>restart your device</b> to solve it.<br />
Due to some reasons, the push service extension [UNNotificationServiceExtension](https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension) failed to run normally, and the code for saving notifications was not executed properly.
#### Automatic copy push failure
After iOS 14.5 version, due to permission tightening, it is not possible to automatically copy push content to clipboard when receiving push. <br/>
You can temporarily pull down push or swipe left on lock screen and click view to automatically copy, or click copy button on pop-up push.
#### Open notification history page by default
When you open APP again, it will jump to the last opened page.<br />
Just exit APP when you are on history message page. When you open APP again, it will be history message page.
#### Does push API support POST request?
Bark supports GET POST , supports using Json <br>
No matter which request method, parameter names are the same. Refer to [usage tutorial](/tutorial)
#### Pushing special characters causes push failure. For example: Push content contains link or Push abnormal such as + becomes space
This is because of the problem of irregular link. It often happens<br>
When splicing URL, pay attention to URL encoding parameters
```sh
# For example
https://api.day.app/key/{push content}
# If {push content} is
"a/b/c/"
# Then the final spliced URL is
https://api.day.app/key/a/b/c/
# The corresponding route will not be found and the backend program will return 404
#You should url encode {push content} before splicing
https://api.day.app/key/a%2Fb%2Fc%2F
```
If you use a mature HTTP library, parameters will be automatically processed and you dont need manual encoding. <br>
But if you splice URL yourself, you need special attention for special characters in parameters. **Its better not care whether there are special characters or not and blindly apply a layer of URL encoding.**
#### How to ensure privacy and security
Refer [privacy security](/en-us/privacy)

1
docs/en-us/privacy.md Normal file
View File

@ -0,0 +1 @@
...

1
docs/en-us/tutorial.md Normal file
View File

@ -0,0 +1 @@
...

37
docs/encryption.md Normal file
View File

@ -0,0 +1,37 @@
#### 什么是推送加密
推送加密是一种保护推送内容的方法,它使用自定义秘钥在发送和接收时对推送内容进行加密和解密。<br>这样,推送内容在传输过程中就不会被 Bark 服务器和苹果 APNs 服务器获取或泄露。
#### 设置自定义秘钥
1. 打开APP首页
2. 找到 “推送加密” ,点击加密设置
3. 选择加密算法按要求填写KEY点击完成保存自定义秘钥
#### 发送加密推送
要发送加密推送,首先需要把 Bark 请求参数转换成 json 格式的字符串然后用之前设置的秘钥和相应的算法对字符串进行加密最后把加密后的密文作为ciphertext参数发送到服务器。<br><br>
**示例:**
```sh
#!/usr/bin/env bash
set -e
# bark key
deviceKey='F5u42Bd3HyW8KxkUqo2gRA'
# push payload
json='{"body": "test", "sound": "birdsong"}'
# must be 16 bit long
key='1234567890123456'
iv='1111111111111111'
# openssl requires Hex encoding of manual keys and IVs, not ASCII encoding.
key=$(printf $key | xxd -ps -c 200)
iv=$(printf $iv | xxd -ps -c 200)
ciphertext=$(echo -n $json | openssl enc -aes-128-cbc -K $key -iv $iv | base64)
# The console will print "d3QhjQjP5majvNt5CjsvFWwqqj2gKl96RFj5OO+u6ynTt7lkyigDYNA3abnnCLpr"
echo $ciphertext
# URL encoding the ciphertext, there may be special characters.
curl --data-urlencode "ciphertext=$ciphertext" http://api.day.app/$deviceKey
```

51
docs/faq.md Normal file
View File

@ -0,0 +1,51 @@
#### 江苏移动用户提示“服务器错误,不能使用推送服务” <!-- {docsify-ignore-all} -->
江苏移动存在DNS解析污染可以更换DNS、翻墙、换网络或者更换 bark 服务器域名 https://api1.day.app ,<br>
api1 与 api 除了域名不一样其他完全一样,是同一台服务器可以无痛切换<br>
如果已经注册了设备,则只要保证发送端能正常访问 api.day.app 即可APP无需连接服务器也能正常接收推送。
#### 推送使用次数限制
正常请求HTTP状态码为200无任何限制。<br>
但如果在5分钟内超过1000次错误请求HTTP状态码为400 404 500<b>IP会被 BAN 24小时</b>
#### 时效性通知无效
可以尝试<b>重启设备</b>来解决。
#### 无法保存通知历史,或下拉推送没有点击复制按钮无法复制
可以尝试<b>重启设备</b>来解决。<br />
因某些原因导致推送服务扩展([UNNotificationServiceExtension](https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension))未能正常运行,执行通知保存的代码未能正常执行。
#### 自动复制推送失效
iOS 14.5 之后的版本因权限收紧,不能在收到推送时自动复制推送内容到剪切板。<br/>
可暂时先下拉推送或在锁屏界面左滑推送点查看即可自动复制,或点击弹出的推送复制按钮。
#### 默认打开通知历史列表
再次开启APP时会跳转到上次打开的页面。<br />
只需退出APP时停留在历史消息页面再次打开APP时就是历史消息页面。
#### 推送 API 是否支持 POST 请求?
Bark支持 GET POST ,支持使用Json<br>
无论哪种请求方式,参数名都一样, 参考[使用教程](/tutorial#请求方式)
#### 推送特殊字符导致推送失败,比如 推送内容包含链接,或推送异常 比如 + 变成空格
这是因为整个链接不规范导致的问题常发生在自己手动拼接URL时。<br>
拼接URL时注意将参数进行URL编码
```sh
# 例如
https://api.day.app/key/{推送内容}
# 如果{推送内容}是
"a/b/c/"
# 则最后拼接的URL是
https://api.day.app/key/a/b/c/
# 将找不到对应的路由后端程序将返回404
# 应该将 {推送内容} url编码后再进行拼接
https://api.day.app/key/a%2Fb%2Fc%2F
```
如果是使用成熟的HTTP库时参数都会被自动处理无需自己手动编码。<br>
但如果是自己去拼接URL时则需要特别注意参数中的特殊字符**最好不管有没有特殊字符无脑套一层URL编码**。
#### 如何保障隐私安全
参考[隐私安全](/privacy)

34
docs/index.html Normal file
View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bark</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: 'Bark',
nameLink: '/#/?id=源码',
// repo: 'https://github.com/Finb/Bark',
logo: '/_media/Icon.png height=60px',
coverpage: true,
loadSidebar: true,
subMaxLevel: 4,
loadNavbar: true,
auto2top: true,
coverpage: ['/', '/en-us/', '/tr/'],
}
</script>
<!-- Docsify v4 -->
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
</body>
</html>

31
docs/privacy.md Normal file
View File

@ -0,0 +1,31 @@
#### 隐私如何泄露 <!-- {docsify-ignore-all} -->
一条推送从发送到接收经过路线是:<br>
发送端 <font color='red'> →服务端①</font> → 苹果APNS服务器 → 你的设备 → <font color='red'>Bark APP②</font>
红色的两处地方可能泄露隐私 <br>
* 发送端未使用HTTPS或使用公共服务器*(作者会看到请求日志)*
* Bark App 本身不安全,上传到 App Store 的版本经过修改。
#### 解决服务端隐私问题
* 你可以使用开源的后端代码,自行[部署后端服务](/deploy.md)开启HTTPS。
* 使用自定义秘钥的[加密推送](/encryption) ,加密推送内容
#### 保证 APP 完全由开源代码构建
为确保 App 是安全、未经任何人包含作者修改过的Bark 是由 GitHub Actions 构建后上传到 App Store。<br>
Bark应用设置内可以查看到 GitHub Run Id点击可在里面找到当前版本构建所使用的配置文件、编译时的源代码、上传到 App Store 的版本 build 号 等等信息。<br>
同一个版本 build 号仅能上传到 App Store 一次,所以这个号是唯一的。<br>
可用此号对比从商店下载的 Bark App如果一致则证明从 App Store 下载的 App 是完全由开源代码构建。
举例: Bark 1.2.9 - 3 <br>
https://github.com/Finb/Bark/actions/runs/3327969456
1. 找到编译时的 commit id ,可以查看编译时完整的源码
2. 查看 .github/workflows/testflight.yaml ,验证所有 Action ,确保 Action 打印的日志未被篡改
3. 查看 Action Logs https://github.com/Finb/Bark/actions/runs/3327969456/jobs/5503414528
4. 找到 打包的App ID、Team ID、上传到 App Store 的版本与 build 号等信息。
5. 下载商店对应版本ipa比对版本build号是否与日志中一致*这个号码同一个APP是唯一的成功上传了就不能再以相同的版本build号上传*
*这里不考虑iOS是否泄露隐私*

34
docs/tr/README.md Normal file
View File

@ -0,0 +1,34 @@
### Bark <!-- {docsify-ignore-all} -->
- Ücretsiz, hafif! Kendi iPhone'unuza anlık bildirimler göndermek için arayüzü çağırmanız yeterlidir.
- Apple APN'lerine bağlıdır, zamanında, istikrarlı ve güvenilirdir
- Cihazın pil gücünü tüketmez. Sistem anlık bildirim hizmeti ve anlık bildirim uzantısına bağlı olarak, APP'nin kendisinin çalışması gerekmez.
- Gizlilik ve güvenlik. Yazarın kendisi de dahil olmak üzere hiç kimsenin bazı yollarla gizliliğinizi çalamayacağından emin olabilirsiniz.<br>*[Gizlilik ve güvenliğin](/tr/privacy) nasıl sağlanacağına ilişkin ayrıntılar için tıklayın*
### Kaynak Kodu
- [Bark](https://github.com/Finb/Bark) özel anlık bildirimler almak için tamamen açık kaynaklı bir iOS uygulamasıdır
- [bark-server](https://github.com/Finb/bark-server) kullanıcı anlık bildirim isteklerini almak ve bunları Apple APNS'ye iletmek için tamamen açık kaynaklı bir Bark hizmeti arka ucudur.
### Geri Bildirim
- [Telegram Grubu](https://t.me/joinchat/OsCbLzovUAE0YjY1) Not: gruba katılmak için doğrulamaya tıklayın
- [GitHub Sorunlar](https://github.com/Finb/Bark/issues)
### Ücretsiz
Bark **Temmuz 2018**'de başlatıldı ve en az **Temmuz 2031**'e kadar işletilmeye devam edecek. *("Kalıcı" kelimesini söyleyemem, hala talep varsa daha sonra yenileyelim.)*<br>
Uygulama, bakım süresi boyunca herhangi bir şekilde ücretlendirilmeyecek veya reklam içermeyecektir. Kullanmaktan çekinmeyin.
### Sponsorluk
Şu anda yalnızca GitHub sponsorluğunu kabul ediyoruz. Her sponsor için çok teşekkür ederim<br>
Sponsorlar[https://github.com/sponsors/Finb](https://github.com/sponsors/Finb)
### Dokümantasyon
- **Uygulama**
- [Öğretici](/tr/tutorial)
- [Şifreleme](/tr/encryption)
- [SSS](/tr/faq)
- **Sunucu**
- [Dağıtım](/tr/deploy)
- [Doğrudan Gönderim](/tr/apns)
- [Oluşturmak](/tr/build)
- [Sertifika](/tr/cert)
- [Gizlilik](/tr/privacy)

11
docs/tr/_coverpage.md Executable file
View File

@ -0,0 +1,11 @@
![logo](../_media/Icon.png)
# Bark <small></small>
> Gizlilik odaklı, güvenli ve kontrol edilebilir bir özel anlık bildirim aracı.
- Ücretsiz, basit ve güvenli
- Kullanıma hazır
[GitHub](https://github.com/finb/bark)
[Başlamak](#bark)

11
docs/tr/_sidebar.md Normal file
View File

@ -0,0 +1,11 @@
- [Bark](/tr/#bark)
- **Uygulama**
- [Öğretici](/tr/tutorial)
- [Şifreleme](/tr/encryption)
- [SSS](/tr/faq)
- **Sunucu**
- [Dağıtım](/tr/deploy)
- [Doğrudan Gönderim](/tr/apns)
- [Oluşturmak](/tr/build)
- [Sertifika](/tr/cert)
- [Gizlilik](/tr/privacy)

55
docs/tr/apns.md Normal file
View File

@ -0,0 +1,55 @@
### APNS Arayüzünü Doğrudan Kullanma
Eğer cihazınızın DeviceToken'ı varsa (Uygulama içinde bulunabilir), doğrudan Apple APNS arayüzünü çağırarak cihaza anlık bildirim gönderebilirsiniz. Ayrıca, Uygulamaya sunucu eklemeye gerek yoktur.<br>
Aşağıda komut satırında anlık bildirim gönderme örneği verilmiştir:
```shell
# Çevresel değişkenleri ayarla
# Key'i indir https://raw.githubusercontent.com/Finb/bark-server/master/deploy/AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8
# Key dosyasının yolunu aşağıya gir
TOKEN_KEY_FILE_NAME=
# Uygulama ayarlarından kopyalanan DeviceToken'ı buraya yapıştır
DEVICE_TOKEN=
# Aşağıdakileri değiştirmeyin !!!
TEAM_ID=5U8LBRXG3A
AUTH_KEY_ID=LH4T9V5U4R
TOPIC=me.fin.bark
APNS_HOST_NAME=api.push.apple.com
# TOKEN oluştur
JWT_ISSUE_TIME=$(date +%s)
JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}"
JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
# Eğer koşullar elverişliyse, en iyi performans için bu betiği geliştirerek TOKEN'ı önbelleğe almanız iyi olur.
# Aynı TOKEN'i 30 dakika içinde tekrar kullanmak, her 30 dakikada bir yeniden oluşturmaktan daha iyidir.
# Apple dokümantasyonu, TOKEN'ın en erken 20 dakika arayla yeniden oluşturulabileceğini belirtmektedir. TOKEN'ın maksimum geçerlilik süresi 60 dakikadır.
# Ancak, sık sık yeniden oluşturmak TOKEN'ın başarısız olmasına neden olabilir.
# Bu bilgiyi paylaşmak istiyoruz, belki sık sık TOKEN oluşturmak nedeniyle anlık bildirim gönderiminde sorun yaşanabilir.
AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"
# Anlık bildirim gönderme
curl -v --header "apns-topic: $TOPIC" --header "apns-push-type: alert" --header "authorization: bearer $AUTHENTICATION_TOKEN" --data '{"aps":{"alert":"test"}}' --http2 https://${APNS_HOST_NAME}/3/device/${DEVICE_TOKEN}
```
### Push Parametre Formatı
Bknz. https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification<br>
"mutable-content" : 1 getirdiğinizden emin olun, aksi takdirde anlık bildirim uzantısı çalışmayacak ve anlık bildirimi kaydetmeyecektir.
Örnek
```js
{
"aps": {
"mutable-content": 1,
"alert": {
"title" : "Başlık",
"body": "İçerik"
},
"category": "bildirimGrubu",
"sound": "minuet.caf"
},
"icon": "https://day.app/assets/images/avatar.jpg"
}
```

47
docs/tr/build.md Normal file
View File

@ -0,0 +1,47 @@
## Kaynak Kodu İndirme
GitHub'dan kaynak kodunu indirin: [bark-server](https://github.com/Finb/bark-server)
veya
```sh
git clone https://github.com/Finb/bark-server.git
```
## Gerekli Bağımlılıkları Yapılandırma
- Golang 1.18+
- Go Mod (env GO111MODULE=on)
- Go Mod Proxy (env GOPROXY=https://goproxy.cn)
- [go-task](https://taskfile.dev/installation/) kurulu olmalı.
## Tüm Platformlar İçin Çapraz Derleme
```sh
task
```
## Belirli Bir Platform İçin Derleme
```sh
task linux_amd64
task linux_amd64_v3
```
## Desteklenen Platformlar
- linux_386
- linux_amd64
- linux_amd64_v2
- linux_amd64_v3
- linux_amd64_v4
- linux_armv5
- linux_armv6
- linux_armv7
- linux_armv8
- linux_mips_hardfloat
- linux_mipsle_softfloat
- linux_mipsle_hardfloat
- linux_mips64
- linux_mips64le
- windows_386.exe
- windows_amd64.exe
- windows_amd64_v2.exe
- windows_amd64_v3.exe
- windows_amd64_v4.exe
- darwin_amd64
- darwin_arm64

6
docs/tr/cert.md Normal file
View File

@ -0,0 +1,6 @@
Bark'ı kendi sistemine entegre etmeniz veya arka uç kodunu yeniden uygulamanız gerektiğinde, anlık bildirim sertifikasına ihtiyacınız olabilir.
##### Geçerlilik Süresi: *Süresiz*
##### Anahtar Kimliği (Key ID)*LH4T9V5U4R*
##### Ekip Kimliği (TeamID)*5U8LBRXG3A*
##### İndirme Bağlantısı[AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8](https://github.com/Finb/bark-server/releases/download/v1.0.2/AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8)

86
docs/tr/deploy.md Normal file
View File

@ -0,0 +1,86 @@
## Docker
```
docker run -dt --name bark -p 8080:8080 -v `pwd`/bark-data:/data finab/bark-server
```
## Docker-Compose
```
mkdir bark && cd bark
curl -sL https://git.io/JvSRl > docker-compose.yaml
docker-compose up -d
```
## Manuel Dağıtım
1. Platforma göre çalıştırılabilir dosyayı indirin.<br> <a href='https://github.com/Finb/bark-server/releases'>https://github.com/Finb/bark-server/releases</a><br>
veya kendiniz derleyin<br>
<a href="https://github.com/Finb/bark-server">https://github.com/Finb/bark-server</a>
2. Çalıştırın
```
./bark-server_linux_amd64 -addr 0.0.0.0:8080 -data ./bark-data
```
3. Gerekebilir
```
chmod +x bark-server_linux_amd64
```
Lütfen unutmayın ki bark-server varsayılan olarak verileri saklamak için /data dizinini kullanır. Bark-server'ın /data dizinine okuma ve yazma izinlerine sahip olduğundan emin olun veya farklı bir dizini belirtmek için `-data` seçeneğini kullanabilirsiniz.
## Sunucusuz Mimari (Serverless)
Heroku ücretsiz dağıtım sunar (2022-11-28 tarihine kadar).<br>
[![Dağıtım](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/finb/bark-server)<br>
Web yönlendirmelerini destekleyen diğer serverless sunucuları, Serverless modunu etkinleştirmek için `bark-server -serverless true` komutunu kullanabilirsiniz.
Etkinleştirildiğinde, bark-server sistem ortam değişkenleri BARK_KEY ve BARK_DEVICE_TOKEN'ı okuyacaktır. Bu değerleri önceden ayarlamanız gerekmektedir.
| Değişken Adı | Gereksinimler |
| ---- | ---- |
| BARK_KEY | "push" dışında herhangi bir değer girilebilir. |
| BARK_DEVICE_TOKEN | Bark App ayarlarında görünen DeviceToken. Bu Token gerçek APNS cihaz tokenidir, lütfen sızdırmayın. |
Lütfen Serverless modunun yalnızca bir cihaza izin verdiğini unutmayın.
## Render
Render, ücretsiz bir bark-server oluşturmayı çok kolay hale getirir.
1. [Render](https://dashboard.render.com/register/) hesabı oluşturun.
2. [Yeni Web Hizmeti](https://dashboard.render.com/select-repo?type=web) oluşturun.
3. Aşağıdaki URL'yi **Public Git repository** giriş kutusuna girin
```
https://github.com/Finb/bark-server
```
4. **Devam et**'i tıklayın ve formu doldurun
* **Name** - Ad, herhangi bir ad seçin, örneğin bark-server.
* **Region** - Sunucu bölgesi, size en yakın olanı seçin.
* **Start Command** - Programı çalıştırma komutu, `./app -serverless true` şeklinde doldurun. (Lütfen ./app öncesindeki noktayı unutmayın)
* **Instance Type** - *Free*'yi seçin, ücretsiz olan yeterlidir.
* Seçenekleri genişletmek için **Advanced**'e tıklayın.
* **Add Environment Variable**'a tıklayarak Serverless modu için gereken BARK_KEY ve BARK_DEVICE_TOKEN alanlarını ekleyin. (Gereksinimler için [Serverless](#Serverless) bölümüne bakın) <br><img src="_media/environment.png" />
* Diğer seçenekler değiştirilmez
5. Sayfanın alt kısmında **Create Web Service** düğmesine tıklayın ve durumun **In progress**'ten **Live**'a geçmesini bekleyin, bu birkaç dakika ila on dakika sürebilir.
6. Sayfanın üst kısmında sunucu URL'nizi bulun, bu bark-server sunucu URL'si, Bark App'e ekleyebilirsiniz
```
https://[sizin-sunucu-adiniz].onrender.com
```
7. Sunucu ekleme başarısız olursa bir süre bekleyebilir ve tekrar deneyebilirsiniz. Servis henüz hazır olmayabilir.
8. Bark App'e ekleme yapmanıza gerek yoktur, doğrudan çağrı yaparak anlık bildirim gönderebilirsiniz. BARK_KEY yukarıda doldurduğunuz anahtardır.
```
https://[sizin-sunucu-adiniz].onrender.com/BARK_KEY/推送内容
```
## Test
```
curl http://0.0.0.0:8080/ping
```
Eğer **pong** dönerse, dağıtım başarılı demektir.
## Diğer
1. Uygulama tarafı sunucu tarafına <a href="https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application">DeviceToken</a>göndermekten sorumludur.<br>Sunucu tarafı bir push isteği aldığında, Apple sunucusuna bir anlık bildirim gönderir. Ardından cep telefonu anlık bildirimi alır.
2. Sunucu kodu:<a href='https://github.com/Finb/bark-server'>https://github.com/Finb/bark-server</a><br>
3. Uygulama Kodu: <a href="https://github.com/Finb/Bark">https://github.com/Finb/Bark</a>

36
docs/tr/encryption.md Normal file
View File

@ -0,0 +1,36 @@
#### Anlık Bildirim şifreleme nedir?
Anlık bildirim şifreleme, Anlık bildirim içeriğini korumak için bir yöntemdir ve gönderme ve alma sırasında Anlık bildirim içeriğini şifrelemek ve şifresini çözmek için özel bir anahtar kullanır. Bu şekilde, Anlık bildirim içeriği iletim sırasında Bark sunucusu ve Apple APNs sunucusu tarafından elde edilemez veya sızdırılamaz.
#### Özel anahtar ayarlama
1. Uygulama ana sayfasınıın.
2. "Anlık Bildirim Şifreleme"yi bulun, Şifreleme Ayarları'na tıklayın.
3. Şifreleme algoritmasını seçin, ANAHTARI gerektiği gibi doldurun, özel anahtarı kaydetmek için Bitti'ye tıklayın.
#### Şifreli anlık bildirim gönderme
Şifrelenmiş bir anlık bildirim göndermek için önce Bark istek parametrelerini json formatında bir dizeye dönüştürmeniz, ardından dizeyi şifrelemek için önceden ayarlanmış anahtarı ve ilgili algoritmayı kullanmanız ve son olarak şifrelenmiş şifre metnini sunucuya "ciphertext" parametresi olarak göndermeniz gerekir.<br><br>
**Örneğin**
```sh
#!/usr/bin/env bash
set -e
# bark anahtarı
deviceKey='F5u42Bd3HyW8KxkUqo2gRA'
# anlık bildirim gönderilen veri
json='{"body": "test", "sound": "birdsong"}'
# 16 bit uzunluğunda olmalıdır
key='1234567890123456'
iv='1111111111111111'
# openssl, manuel anahtarların ve IV'lerin ASCII kodlamasını değil, Hex kodlamasını gerektirir.
key=$(printf $key | xxd -ps -c 200)
iv=$(printf $iv | xxd -ps -c 200)
ciphertext=$(echo -n $json | openssl enc -aes-128-cbc -K $key -iv $iv | base64)
# Konsol şunları yazdıracaktır "d3QhjQjP5majvNt5CjsvFWwqqj2gKl96RFj5OO+u6ynTt7lkyigDYNA3abnnCLpr"
echo $ciphertext
curl --data-urlencode "ciphertext=$ciphertext" http://api.day.app/$deviceKey
```

46
docs/tr/faq.md Normal file
View File

@ -0,0 +1,46 @@
#### Anlık bildirim kullanım limiti <!-- {docsify-ignore-all} -->
Normal isteklerin (HTTP durum kodu 200) sınırı yoktur.<br>
Ancak 5 dakika içinde 1000'den fazla hata isteği (HTTP durum kodu 400 404 500) yapılırsa, <b>IP 24 saat boyunca YASAKLANACAKTIR.</b>
#### Zamana duyarlı bildirimler çalışmıyor
Sorunu çözmek için <b>cihazınızı yeniden başlatmayı</b> deneyebilirsiniz.
#### Bildirim geçmişi kaydedilemiyor veya kopyala düğmesine tıklamadan kilit ekranında aşağı iterek veya sola kaydırarak kopyalanamıyor
Sorunu çözmek için <b>cihazınızı yeniden başlatmayı</b> deneyebilirsiniz.<br />
Bazı nedenlerden dolayı, anlık bildirim hizmeti uzantısı ([UNNotificationServiceExtension](https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension)) düzgün bir şekilde çalıştırılamadı ve bildirimleri kaydetme kodu düzgün bir şekilde yürütülmedi.
#### Otomatik kopyalama anlık bildirim hatası
iOS 14.5 sürümünden sonra, izinlerin sıkılaştırılması nedeniyle, push bildirimi alırken içeriği otomatik olarak panoya kopyalamak mümkün değildir. <br/>
Geçici olarak push bildirimini aşağı çekebilir veya ekran kilidinde sola kaydırarak görüntüleyip otomatik olarak kopyalayabilirsiniz, veya açılır penceredeki kopyala düğmesine tıklayabilirsiniz.
#### Bildirim geçmişi sayfasını varsayılan olarak aç
Uygulamayı tekrar açtığınızda, son açılan sayfaya atlayacaktır.<br />
Eğer geçmiş mesaj sayfasındaysanız, uygulamadan çıkın. Uygulamayı tekrar açtığınızda yine geçmiş mesaj sayfasında olacaksınız.
#### Anlık Bildirim API POST isteğini destekliyor mu?
Bark GET POST'u destekler, Json kullanımını destekler.<br>
Hangi istek yöntemi olursa olsun, parametre adları aynıdır. Bakınız [kullanım öğreticisi](tr/tutorial)
#### Özel karakterler anlık bildirim işleminde başarısızlığa neden oluyor. Örneğin: İtme içeriği bağlantı içeriyor veya + gibi özel karakterler boşluğa dönüşüyor.
Bunun nedeni düzensiz bağlantı sorunudur. Sıklıkla olur<br>
URL eklerken, URL kodlama parametrelerine dikkat edin
```sh
# Örneğin
https://api.day.app/key/{push content}
# Eğer {anlık bildirim içeriği} ise
"a/b/c/"
# O zaman URLnin son durumu şöyledir
https://api.day.app/key/a/b/c/
# İlgili rota bulunamaz ve arka uç programı 404 döndürür
#Eklemeden önce {anlık bildirim içeriği} öğesini url kodlamalısınız
https://api.day.app/key/a%2Fb%2Fc%2F
```
Gelişmiş bir HTTP kütüphanesi kullanırsanız, parametreler otomatik olarak işlenecek ve manuel kodlamaya ihtiyaç duymayacaksınız. <br>
Ancak URL'yi kendiniz eklerseniz, parametrelerdeki özel karakterler için özel dikkat göstermeniz gerekir. **Özel karakterlerin varlığını dikkate almadan ve otomatik olarak URL kodlama katmanı uygulamak genellikle daha iyi bir yaklaşımdır.**
#### Gizlilik ve güvenlik nasıl sağlanır?
Bakınız [gizlilik güvenlik](/tr/privacy)

31
docs/tr/privacy.md Normal file
View File

@ -0,0 +1,31 @@
#### Gizlilik Nasıl Sızdırılır? <!-- {docsify-ignore-all} -->
Bir anlık bildirimin gönderilmesinden alınmasına kadar geçtiği yol şöyledir:<br />
Gönderici<font color='red'> → Sunucu①</font> → Apple APNS sunucusu → Aygıtınız → <font color='red'>Bark Uygulaması</font>
Kırmızıyla işaretlenen bu iki yerde gizlilik sızdırma riski olabilir:<br>
* Gönderen HTTPS kullanmıyor veya herkese açık bir sunucu kullanıyor (yazarlar istek günlüklerini görecektir)
* Bark Uygulaması kendisi güvenli değilse, App Store'a yüklenen sürümde değişiklikler yapılmış olabilir.
#### Sunucu Gizlilik Sorununu Nasıl Çözebilirsiniz
* Açık kaynaklı sunucu kodunu kullanarak [kendi sunucunuzu kurarak](/tr/deploy.md) ve HTTPS'yi etkinleştirerek.
* [Şifreli Push Bildirimi](/tr/encryption) kullanarak, içerikleri şifreleyebilirsiniz.
#### Uygulamanın Tamamen Açık Kaynak Kodlarla İnşa Edildiğini Sağlama
Uygulamanın güvenli olmasını ve hiç kimse tarafından (yazar dahil) değiştirilmemesini sağlamak için Bark, GitHub Actions tarafından oluşturulur ve ardından App Store'a yüklenir.<br />
GitHub Çalıştırma Kimliği, Bark uygulama ayarlarında bulunabilir; burada derlemenin geçerli sürümü için kullanılan yapılandırma dosyası, derlemede kullanılan kaynak kodu, App Store'a yüklenen sürümün derleme numarası ve daha fazlası hakkında bilgi bulabilirsiniz.<br>
Aynı derleme numarası App Store'a yalnızca bir kez yüklenebilir, bu nedenle benzersizdir.<br>
Bu numarayı, Mağazadan indirilen Bark Uygulamalarını karşılaştırmak için kullanabilirsiniz ve eşleşirse, App Store'dan indirilen Uygulamanın tamamen açık kaynak kodundan oluşturulduğunu kanıtlar.
Örnek: Bark 1.2.9 - 3<br>
https://github.com/Finb/Bark/actions/runs/3327969456
1. Derleme sırasındaki commit kimliğini bulun ve derleme sırasında kullanılan kaynak kodlarını görüntüleyin.
2. .github/workflows/testflight.yaml dosyasını inceleyin, tüm işlemleri doğrulayın ve bu işlemlerin kayıtlarının değiştirilmediğinden emin olun.
3. Action Günlüklerini görüntüleyin: https://github.com/Finb/Bark/actions/runs/3327969456/jobs/5503414528
4. Paketlenen Uygulama Kimliği (App ID), Ekip Kimliği (Team ID), App Store'a yüklenen sürüm ve derleme numarası gibi bilgileri bulun.
5. Mağaza için ilgili sürümün IPA dosyasını indirin ve derleme numarasını kayıtlardakiyle karşılaştırın *(Bu numara her uygulama için benzersizdir ve bir kez yüklendikten sonra aynı derleme numarasıyla başka bir sürüm yüklenemez)*.
*Bu kapsamda iOS'un gizlilik açısından incelenmesi dikkate alınmamıştır*

71
docs/tr/tutorial.md Normal file
View File

@ -0,0 +1,71 @@
## Anlık Bildirim Gönderimi
1. Uygulamayıın, test URL'sini kopyalayın.
<img src="_media/example.jpg" width=365 />
2. İçeriği değiştirin ve URL'ye istek atın.<br>
GET veya POST isteği gönderebilirsiniz, istek başarılıysa hemen bir push alacaksınız.
## URL Formatı
URL, bir anlık bildirim anahtarı, "title" parametresi ve "body" parametresinden oluşur. İki farklı kombinasyon şekli vardır:
```
/:key/:body
/:key/:title/:body
```
## İstek Yöntemi
##### GET isteği parametreleri URL'nin sonuna eklenir, örneğin:
```sh
curl https://api.day.app/your_key/BildirimIcerigi?group=Grup&copy=KopyalanacakIcerik
```
*Elle parametreleri URL'ye eklerken URL kodlama sorunlarına dikkat etmelisiniz, [Sıkça Sorulan Sorular: URL Kodlama](tr/faq?id=%C3%96zel-karakterler-anl%C4%B1k-bildirim-i%C5%9Fleminde-ba%C5%9Far%C4%B1s%C4%B1zl%C4%B1%C4%9Fa-neden-oluyor-%C3%96rne%C4%9Fin-%C4%B0tme-i%C3%A7eri%C4%9Fi-ba%C4%9Flant%C4%B1-i%C3%A7eriyor-veya-gibi-%C3%B6zel-karakterler-bo%C5%9Flu%C4%9Fa-d%C3%B6n%C3%BC%C5%9F%C3%BCyor)*
##### POST isteği parametreleri istek gövdesine yerleştirilir, örneğin:
```sh
curl -X POST https://api.day.app/your_key \
-d'body=Push İçeriği&group=Grup&copy=Kopyala'
```
##### POST isteği JSON'ı destekler, örneğin:
```sh
curl -X "POST" "https://api.day.app/your_key" \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{
"body": "Test Bark Server",
"title": "Test Başlık",
"badge": 1,
"category": "benimBildirimGrubum",
"sound": "minuet.caf",
"icon": "https://day.app/assets/images/avatar.jpg",
"group": "test",
"url": "https://mritd.com"
}'
```
##### JSON isteği anahtarı istek gövdesine yerleştirilebilir, URL yolu */push* olmalıdır, örneğin
```sh
curl -X "POST" "https://api.day.app/push" \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{
"body": "Test Bark Server",
"title": "Test Başlık",
"device_key": "sizin_anahtarınız"
}'
```
## İstek Parametreleri
Desteklenen parametrelerin listesi, belirli bir etkiyi uygulamada nasıl görüneceğini görmek için uygulama içinden önizleme yapabilirsiniz.
| Parametre | Açıklama |
| ----- | ----------- |
| title | Anlık bildirim başlığı |
| body | Anlık bildirim içeriği |
| level | Anlık bildirim kesme seviyesi. <br>**active:** Varsayılan değer, sistem bildirimi hemen göstermek için ekranı aydınlatacaktır.<br>**timeSensitive:** Zamana duyarlı bildirim, odaklanmış durumda bildirim gösterebilir.<br>**passive:** Bildirimi yalnızca bildirim listesine ekler, hatırlatmak için ekranı aydınlatmaz. |
| badge | Anlık bildirim rozeti, herhangi bir sayı olabilir. |
| autoCopy | iOS 14.5'ten önce otomatik olarak anlık bildirim içeriğini kopyalar, iOS 14.5'ten sonraysa manuel olarak uzun basmalı veya anlık bildirim aşağı çekilmelidir |
| copy | Bir anlık bildirim kopyalanırken, kopyalanacak içeriği belirtin; bu parametre belirtilmezse tüm anlık bildirim içeriğini kopyalar. |
| sound | Anlık bildirim için bir ses seçebilirsiniz. |
| icon | Anlık bildirim için özel bir simge ayarlayın ve ayarlanan simge varsayılan Bark simgesinin yerini alacaktır. Simgeler otomatik olarak yerel olarak önbelleğe alınır ve aynı simge URL'si yalnızca bir kez indirilir. |
| group | Bildirimleri gruplandırın, anlık bildirimler gruplanmış bir şekilde bildirim merkezinde görüntülenir.<br>Ayrıca geçmiş mesajlar listesinde farklı grupları görüntülemeyi de seçebilirsiniz. |
| isArchive | 1 ile gönderirseniz, anlık bildirim kaydedilir; diğer bir değer gönderirseniz kaydedilmez. Göndermezseniz, kaydetme ayarlarına göre karar verilir. |
| url | Anlık bildirime tıklanınca gidilecek URL, URL Şeması ve Evrensel Bağlantıları destekler. |

71
docs/tutorial.md Normal file
View File

@ -0,0 +1,71 @@
## 发送推送
1. 打开APP复制测试URL
<img src="../_media/example.jpg" width=365 />
2. 修改内容请求这个URL。<br>
可以发 GET 或者 POST 请求 ,请求成功会立即收到推送
## URL格式
URL由推送key、参数 title、参数 body 组成。有下面两种组合方式
```
/:key/:body
/:key/:title/:body
```
## 请求方式
##### GET 请求参数拼接在 URL 后面,例如:
```sh
curl https://api.day.app/your_key/推送内容?group=分组&copy=复制
```
*手动拼接参数到URL上时请注意URL编码问题可以参考阅读[常见问题URL编码](/faq?id=%e6%8e%a8%e9%80%81%e7%89%b9%e6%ae%8a%e5%ad%97%e7%ac%a6%e5%af%bc%e8%87%b4%e6%8e%a8%e9%80%81%e5%a4%b1%e8%b4%a5%ef%bc%8c%e6%af%94%e5%a6%82-%e6%8e%a8%e9%80%81%e5%86%85%e5%ae%b9%e5%8c%85%e5%90%ab%e9%93%be%e6%8e%a5%ef%bc%8c%e6%88%96%e6%8e%a8%e9%80%81%e5%bc%82%e5%b8%b8-%e6%af%94%e5%a6%82-%e5%8f%98%e6%88%90%e7%a9%ba%e6%a0%bc)*
##### POST 请求参数放在请求体中,例如:
```sh
curl -X POST https://api.day.app/your_key \
-d'body=推送内容&group=分组&copy=复制'
```
##### POST 请求支持JSON例如
```sh
curl -X "POST" "https://api.day.app/your_key" \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{
"body": "Test Bark Server",
"title": "Test Title",
"badge": 1,
"category": "myNotificationCategory",
"sound": "minuet.caf",
"icon": "https://day.app/assets/images/avatar.jpg",
"group": "test",
"url": "https://mritd.com"
}'
```
##### JSON 请求 key 可以放进请求体中,URL 路径须为 /push例如
```sh
curl -X "POST" "https://api.day.app/push" \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{
"body": "Test Bark Server",
"title": "Test Title",
"device_key": "your_key"
}'
```
## 请求参数
支持的参数列表具体效果可在APP内预览。
| 参数 | 说明 |
| ----- | ----------- |
| title | 推送标题 |
| body | 推送内容 |
| level | 推送中断级别。 <br>active默认值系统会立即亮屏显示通知<br>timeSensitive时效性通知可在专注状态下显示通知。<br>passive仅将通知添加到通知列表不会亮屏提醒。 |
| badge | 推送角标,可以是任意数字 |
| autoCopy | iOS14.5以下自动复制推送内容iOS14.5以上需手动长按推送或下拉推送 |
| copy | 复制推送时,指定复制的内容,不传此参数将复制整个推送内容。 |
| sound | 可以为推送设置不同的铃声 |
| icon | 为推送设置自定义图标设置的图标将替换默认Bark图标。<br>图标会自动缓存在本机,相同的图标 URL 仅下载一次。 |
| group | 对消息进行分组推送将按group分组显示在通知中心中。<br>也可在历史消息列表中选择查看不同的群组。 |
| isArchive | 传 1 保存推送传其他的不保存推送不传按APP内设置来决定是否保存。 |
| url | 点击推送时跳转的URL 支持URL Scheme 和 Universal Link |