mirror of
https://github.com/Finb/Bark.git
synced 2025-12-08 21:36:01 +00:00
Compare commits
10 Commits
d97abb5c33
...
1e89b6eb24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e89b6eb24 | ||
|
|
464f3dd454 | ||
|
|
94cae177ba | ||
|
|
ea776ed7d0 | ||
|
|
6c4577e5ea | ||
|
|
89fb58c700 | ||
|
|
7b142915c5 | ||
|
|
98e201773c | ||
|
|
3c78e11125 | ||
|
|
f155f655c7 |
@ -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 {
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -179,6 +179,6 @@ SPEC CHECKSUMS:
|
||||
SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a
|
||||
SwiftyStoreKit: 6b9c08810269f030586dac1fae8e75871a82e84a
|
||||
|
||||
PODFILE CHECKSUM: 5ff0ad6db7c674915eab257bcc3673bde7360e63
|
||||
PODFILE CHECKSUM: e499f90151cf50309319d3c7295e7cb0541725ef
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
|
||||
@ -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 请求 参数名也是上面这些
|
||||
```
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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 ?? "")
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ APP在维持期间,不会有任何形式的收费与广告,各位彦祖放
|
||||
- **服务端**
|
||||
- [部署服务](/deploy)
|
||||
- [直接推送](/apns)
|
||||
- [批量推送](/batch)
|
||||
- [编译代码](/build)
|
||||
- [推送证书](/cert)
|
||||
- [隐私安全](/privacy)
|
||||
@ -6,6 +6,7 @@
|
||||
- **服务端**
|
||||
- [部署服务](/deploy)
|
||||
- [直接推送](/apns)
|
||||
- [批量送送](/batch)
|
||||
- [编译代码](/build)
|
||||
- [推送证书](/cert)
|
||||
- [隐私安全](/privacy)
|
||||
23
docs/batch.md
Normal file
23
docs/batch.md
Normal 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)
|
||||
@ -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服务器。然后手机收到推送
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 | 推送角标,可以是任意数字 |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user