Compare commits

...

10 Commits

Author SHA1 Message Date
Fin
1e89b6eb24 修复自定义图标推送不显示 subtitle 的问题。 2024-12-17 17:25:58 +08:00
Fin
464f3dd454 update doc 2024-12-17 14:33:08 +08:00
Fin
94cae177ba update doc 2024-12-17 14:30:27 +08:00
UUNEO
ea776ed7d0 解决铃声播放问题 2024-12-16 18:43:53 +08:00
Fin
6c4577e5ea 修改为用 Safari 打开 url 参数 2024-12-13 10:56:31 +08:00
Fin
89fb58c700 批量推送文档修改 2024-12-13 10:38:55 +08:00
Fin
7b142915c5 添加批量推送文档 2024-12-13 10:36:50 +08:00
Fin
98e201773c 修复加密推送 subtitle 不正确显示的问题。 2024-12-12 09:45:55 +08:00
Fin
3c78e11125 去掉 category 2024-12-11 18:01:51 +08:00
Fin
f155f655c7 添加 subtitle 支持。close: #48 2024-12-11 17:17:09 +08:00
21 changed files with 132 additions and 25 deletions

View File

@ -74,8 +74,8 @@ class Client: NSObject {
if ["http", "https"].contains(url.scheme?.lowercased() ?? "") {
UIApplication.shared.open(url, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly: true]) { success in
if !success {
// Universal Link safari
self.currentSnackbarController?.present(BarkSFSafariViewController(url: url), animated: true, completion: nil)
// Universal Link safari
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
} else {

View File

@ -14,7 +14,7 @@ let kRealmDefaultConfiguration = {
let fileUrl = groupUrl?.appendingPathComponent("bark.realm")
let config = Realm.Configuration(
fileURL: fileUrl,
schemaVersion: 14,
schemaVersion: 15,
migrationBlock: { migration, oldSchemaVersion in
switch oldSchemaVersion {
case 0...13:

View File

@ -42,7 +42,7 @@ class MessageListViewModel: ViewModel, ViewModelType {
results = results.filter("group in %@", filterGroups)
}
if let text = searchText, text.count > 0 {
results = results.filter("title CONTAINS[c] %@ OR body CONTAINS[c] %@", text, text)
results = results.filter("title CONTAINS[c] %@ OR subtitle CONTAINS[c] %@ OR body CONTAINS[c] %@", text, text, text)
}
return results
}

View File

@ -26,6 +26,10 @@ class SoundsViewController: BaseViewController<SoundsViewModel> {
//
let importSoundActionRelay = PublishRelay<URL>()
// ID
var currentSoundID: SystemSoundID = 0
// ULRL
var playingAudio: CFURL?
override func makeUI() {
self.title = NSLocalizedString("notificationSound")
@ -82,11 +86,24 @@ class SoundsViewController: BaseViewController<SoundsViewModel> {
}).disposed(by: rx.disposeBag)
output.playAction.drive(onNext: { url in
var soundID: SystemSoundID = 0
AudioServicesCreateSystemSoundID(url, &soundID)
AudioServicesPlaySystemSoundWithCompletion(soundID) {
AudioServicesDisposeSystemSoundID(soundID)
}
///
AudioServicesDisposeSystemSoundID(self.currentSoundID)
///
if self.playingAudio == url{
self.playingAudio = nil
self.currentSoundID = 0
return
}
self.playingAudio = url
AudioServicesCreateSystemSoundID(url, &self.currentSoundID)
AudioServicesPlaySystemSoundWithCompletion(self.currentSoundID) {
///
if self.playingAudio == url {
AudioServicesDisposeSystemSoundID(self.currentSoundID)
self.playingAudio = nil
self.currentSoundID = 0
}
}
}).disposed(by: rx.disposeBag)
output.pickerFile.drive(onNext: { [unowned self] _ in

View File

@ -12,6 +12,7 @@ import UIKit
class Message: Object {
@objc dynamic var id = NSUUID().uuidString
@objc dynamic var title: String?
@objc dynamic var subtitle: String?
@objc dynamic var body: String?
@objc dynamic var url: String?
@objc dynamic var group: String?

View File

@ -12,7 +12,6 @@ import UIKit
class PreviewModel: NSObject {
var title: String?
var body: String?
var category: String?
var notice: String?
var queryParameter: String?
var image: UIImage?
@ -21,7 +20,6 @@ class PreviewModel: NSObject {
init(title: String? = nil,
body: String? = nil,
category: String? = nil,
notice: String? = nil,
queryParameter: String? = nil,
image: UIImage? = nil,
@ -30,7 +28,6 @@ class PreviewModel: NSObject {
{
self.title = title
self.body = body
self.category = category
self.notice = notice
self.queryParameter = queryParameter
self.image = image

View File

@ -26,6 +26,7 @@ class ArchiveProcessor: NotificationContentProcessor {
if isArchive {
let alert = (userInfo["aps"] as? [String: Any])?["alert"] as? [String: Any]
let title = alert?["title"] as? String
let subtitle = alert?["subtitle"] as? String
let body = alert?["body"] as? String
let url = userInfo["url"] as? String
let group = userInfo["group"] as? String
@ -33,6 +34,7 @@ class ArchiveProcessor: NotificationContentProcessor {
try? realm?.write {
let message = Message()
message.title = title
message.subtitle = subtitle
message.body = body
message.url = url
message.group = group

View File

@ -27,6 +27,10 @@ class CiphertextProcessor: NotificationContentProcessor {
bestAttemptContent.title = title
alert["title"] = title
}
if let subtitle = map["subtitle"] as? String {
bestAttemptContent.subtitle = subtitle
alert["subtitle"] = subtitle
}
if let body = map["body"] as? String {
bestAttemptContent.body = body
alert["body"] = body

View File

@ -46,18 +46,28 @@ class IconProcessor: NotificationContentProcessor {
suggestionType: .none
)
// subtitle,
let placeholderPerson = INPerson(
personHandle: INPersonHandle(value: "", type: .unknown),
nameComponents: personNameComponents,
displayName: personNameComponents.nickname,
image: avatar,
contactIdentifier: nil,
customIdentifier: nil
)
let intent = INSendMessageIntent(
recipients: [mePerson],
recipients: [mePerson, placeholderPerson],
outgoingMessageType: .outgoingMessageText,
content: bestAttemptContent.body,
speakableGroupName: INSpeakableString(spokenPhrase: personNameComponents.nickname ?? ""),
speakableGroupName: INSpeakableString(spokenPhrase: bestAttemptContent.subtitle),
conversationIdentifier: bestAttemptContent.threadIdentifier,
serviceName: nil,
sender: senderPerson,
attachments: nil
)
intent.setImage(avatar, forParameterNamed: \.sender)
intent.setImage(avatar, forParameterNamed: \.speakableGroupName)
let interaction = INInteraction(intent: intent, response: nil)
interaction.direction = .incoming

View File

@ -179,6 +179,6 @@ SPEC CHECKSUMS:
SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a
SwiftyStoreKit: 6b9c08810269f030586dac1fae8e75871a82e84a
PODFILE CHECKSUM: 5ff0ad6db7c674915eab257bcc3673bde7360e63
PODFILE CHECKSUM: e499f90151cf50309319d3c7295e7cb0541725ef
COCOAPODS: 1.16.2

View File

@ -22,11 +22,11 @@ You can send GET or POST requests, and you'll receive a push notification immedi
URL structure: The first part is the key, followed by three matches
/:key/:body
/:key/:title/:body
/:key/:category/:title/:body
/:key/: title/:subtitle/:body
title: The push title, slightly larger than the body text
subtitle: The push subtitle
body: The push content, use the newline character '\n' for line breaks
category: Reserved for additional features, currently not open for use, just ignore it
For POST requests, the parameter names are the same as above
```

View File

@ -22,11 +22,11 @@ Bark 支持 iOS 通知的多项高级特性,包括推送分组、定制推送
URL 组成: 第一个部分是 key , 之后有三个匹配
/:key/:body
/:key/:title/:body
/:key/:category/:title/:body
/:key/:title/:subtitle/:body
title 推送标题 比 body 字号粗一点
subtitle 推送副标题
body 推送内容 换行请使用换行符 '\n'
category 另外的功能占用的字段,还没开放 忽略就行
post 请求 参数名也是上面这些
```

View File

@ -95,7 +95,7 @@ class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
override func bindViewModel(model: MessageTableViewCellViewModel) {
super.bindViewModel(model: model)
Observable.combineLatest(model.title, model.body, model.url).subscribe {[weak self] title, body, url in
Observable.combineLatest(model.title, model.subtitle, model.body, model.url).subscribe { [weak self] title, subtitle, body, url in
guard let self else { return }
let text = NSMutableAttributedString(
@ -103,6 +103,19 @@ class MessageTableViewCell: BaseTableViewCell<MessageTableViewCellViewModel> {
attributes: [.font: UIFont.preferredFont(ofSize: 14), .foregroundColor: BKColor.grey.darken4]
)
if subtitle.count > 0 {
// spacer
text.insert(NSAttributedString(
string: "\n",
attributes: [.font: UIFont.systemFont(ofSize: 6, weight: .medium)]
), at: 0)
text.insert(NSAttributedString(
string: subtitle + "\n",
attributes: [.font: UIFont.preferredFont(ofSize: 16, weight: .medium), .foregroundColor: BKColor.grey.darken4]
), at: 0)
}
if title.count > 0 {
// spacer
text.insert(NSAttributedString(

View File

@ -24,6 +24,7 @@ class MessageTableViewCellViewModel: ViewModel {
var identity: String
let title: BehaviorRelay<String>
let subtitle: BehaviorRelay<String>
let body: BehaviorRelay<String>
let url: BehaviorRelay<String>
@ -34,6 +35,7 @@ class MessageTableViewCellViewModel: ViewModel {
self.message = message
self.identity = message.id
self.title = BehaviorRelay<String>(value: message.title ?? "")
self.subtitle = BehaviorRelay<String>(value: message.subtitle ?? "")
self.body = BehaviorRelay<String>(value: message.body ?? "")
self.url = BehaviorRelay<String>(value: message.url ?? "")

View File

@ -29,6 +29,7 @@ APP在维持期间不会有任何形式的收费与广告各位彦祖放
- **服务端**
- [部署服务](/deploy)
- [直接推送](/apns)
- [批量推送](/batch)
- [编译代码](/build)
- [推送证书](/cert)
- [隐私安全](/privacy)

View File

@ -6,6 +6,7 @@
- **服务端**
- [部署服务](/deploy)
- [直接推送](/apns)
- [批量送送](/batch)
- [编译代码](/build)
- [推送证书](/cert)
- [隐私安全](/privacy)

23
docs/batch.md Normal file
View File

@ -0,0 +1,23 @@
### 个人用户
暂不支持单个请求推送到多台设备,可通过分别发送的方式实现多设备推送。
### 中间服务
如果你的服务需要大批量且及时地向用户发送推送,建议自建服务端。可以提供 Url Scheme 方便用户一键更改服务器。
Url Scheme 示例:
```
bark://addServer?address=https%3A%2F%2Fapi.day.app
```
bark-server 对配置要求很低,以下是美西 VPS 各配置下的 QPS 测试结果
| Cores | Ram | Speed |
| ----- | ----------- |----------- |
| 1 | 3.75 gb |4,023 p/sec |
| 4 | 16 gb |21,413 p/sec |
| 16 | 64 gb |64,516 p/sec |
| 64 | 256 gb |105,263 p/sec |
若 QPS 不高于 200可继续使用公共服务[https://api.day.app](https://api.day.app))。<br />
若 QPS 超过 200推荐自建服务端未来在公共服务器负载过高时可能会引入流量限制目前尚未限制<br />
若 QPS 超过 3000尽量自建服务端部署时添加 `--max-apns-client-count` 参数,详情请查看[部署文档](/deploy)

View File

@ -26,6 +26,11 @@ chmod +x bark-server_linux_amd64
```
请注意 bark-server 默认使用 /data 目录保存数据,请确保 bark-server 有权限读写 /data 目录,或者你可以使用 `-data` 选项指定一个目录
## Cloudflare Worker
[https://github.com/cwxiaos/bark-worker](https://github.com/cwxiaos/bark-worker)
## Serverless
@ -93,6 +98,37 @@ curl http://0.0.0.0:8080/ping
```
返回 pong 就证明部署成功了
## 大批量推送普通用户忽略QPS超过 3000 再使用)
如果你需要短时间大批量推送,可以配置 bark-server 使用多个 APNS Clients 推送,
每一个 Client 代表一个新的连接可能连接到不同的APNs服务器请根据 CPU 核心数设置这个参数Client 数量不能超过CPU核心数超过会自动设置为当前 CPU 核心数)。
配置方法:
#### Docker
```
docker run -dt --name bark -p 8080:8080 -v `pwd`/bark-data:/data finab/bark-server bark-server --max-apns-client-count 4
```
#### Docker-Compose
```yaml
version: '3.8'
services:
bark-server:
image: finab/bark-server
container_name: bark-server
restart: always
volumes:
- ./data:/data
ports:
- "8080:8080"
command: bark-server --max-apns-client-count 4
```
#### 手动部署
```
./bark-server --addr 0.0.0.0:8080 --data ./bark-data --max-apns-client-count 4
```
## 其他
1. APP端负责将<a href="https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application">DeviceToken</a>发送到服务端。 <br>服务端收到一个推送请求后将发送推送给Apple服务器。然后手机收到推送

View File

@ -47,7 +47,7 @@ Bknz. https://developer.apple.com/documentation/usernotifications/setting_up_a_r
"title" : "Başlık",
"body": "İçerik"
},
"category": "bildirimGrubu",
"category": "myNotificationCategory",
"sound": "minuet.caf"
},
"icon": "https://day.app/assets/images/avatar.jpg"

View File

@ -34,7 +34,6 @@ curl -X "POST" "https://api.day.app/your_key" \
"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",

View File

@ -7,11 +7,12 @@
可以发 GET 或者 POST 请求 ,请求成功会立即收到推送
## URL格式
URL由推送key、参数 title、参数 body 组成。有下面两种组合方式
URL由推送key、参数 title、参数 subtitle、参数 body 组成。有下面三种组合方式
```
/:key/:body
/:key/:title/:body
/:key/:title/:subtitle/:body
```
## 请求方式
@ -34,7 +35,6 @@ curl -X "POST" "https://api.day.app/your_key" \
"body": "Test Bark Server",
"title": "Test Title",
"badge": 1,
"category": "myNotificationCategory",
"sound": "minuet.caf",
"icon": "https://day.app/assets/images/avatar.jpg",
"group": "test",
@ -59,6 +59,7 @@ curl -X "POST" "https://api.day.app/push" \
| 参数 | 说明 |
| ----- | ----------- |
| title | 推送标题 |
| subtitle | 推送副标题 |
| body | 推送内容 |
| level | 推送中断级别。 <br>active默认值系统会立即亮屏显示通知<br>timeSensitive时效性通知可在专注状态下显示通知。<br>passive仅将通知添加到通知列表不会亮屏提醒。 |
| badge | 推送角标,可以是任意数字 |