forked from shadowfacts/Tusker
Add silent x-callback-url actions
User confirmation is required the first time a source app attempts to run an action silently. Rejecting will always display the UI for the given action (as if the silent parameter had been false).
This commit is contained in:
parent
6c3ae9ab14
commit
af0d0612ba
|
@ -95,6 +95,7 @@
|
||||||
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7B2157E01900721E32 /* XCBManager.swift */; };
|
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7B2157E01900721E32 /* XCBManager.swift */; };
|
||||||
D6757A7E2157E02600721E32 /* XCallbackURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7D2157E02600721E32 /* XCallbackURL.swift */; };
|
D6757A7E2157E02600721E32 /* XCallbackURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7D2157E02600721E32 /* XCallbackURL.swift */; };
|
||||||
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A812157E8FA00721E32 /* XCBSession.swift */; };
|
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A812157E8FA00721E32 /* XCBSession.swift */; };
|
||||||
|
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D679C09E215850EF00DA27FE /* XCBActions.swift */; };
|
||||||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; };
|
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; };
|
||||||
D6C94D852139DFD800CB5196 /* LargeImage.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6C94D842139DFD800CB5196 /* LargeImage.storyboard */; };
|
D6C94D852139DFD800CB5196 /* LargeImage.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6C94D842139DFD800CB5196 /* LargeImage.storyboard */; };
|
||||||
|
@ -253,6 +254,7 @@
|
||||||
D6757A7B2157E01900721E32 /* XCBManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBManager.swift; sourceTree = "<group>"; };
|
D6757A7B2157E01900721E32 /* XCBManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBManager.swift; sourceTree = "<group>"; };
|
||||||
D6757A7D2157E02600721E32 /* XCallbackURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCallbackURL.swift; sourceTree = "<group>"; };
|
D6757A7D2157E02600721E32 /* XCallbackURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCallbackURL.swift; sourceTree = "<group>"; };
|
||||||
D6757A812157E8FA00721E32 /* XCBSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBSession.swift; sourceTree = "<group>"; };
|
D6757A812157E8FA00721E32 /* XCBSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBSession.swift; sourceTree = "<group>"; };
|
||||||
|
D679C09E215850EF00DA27FE /* XCBActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActions.swift; sourceTree = "<group>"; };
|
||||||
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSoup.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
|
D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6C94D842139DFD800CB5196 /* LargeImage.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LargeImage.storyboard; sourceTree = "<group>"; };
|
D6C94D842139DFD800CB5196 /* LargeImage.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LargeImage.storyboard; sourceTree = "<group>"; };
|
||||||
|
@ -578,6 +580,7 @@
|
||||||
D6757A7B2157E01900721E32 /* XCBManager.swift */,
|
D6757A7B2157E01900721E32 /* XCBManager.swift */,
|
||||||
D6757A812157E8FA00721E32 /* XCBSession.swift */,
|
D6757A812157E8FA00721E32 /* XCBSession.swift */,
|
||||||
D6757A7D2157E02600721E32 /* XCallbackURL.swift */,
|
D6757A7D2157E02600721E32 /* XCallbackURL.swift */,
|
||||||
|
D679C09E215850EF00DA27FE /* XCBActions.swift */,
|
||||||
);
|
);
|
||||||
path = XCallbackURL;
|
path = XCallbackURL;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -951,6 +954,7 @@
|
||||||
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */,
|
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */,
|
||||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */,
|
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */,
|
||||||
D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */,
|
D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */,
|
||||||
|
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */,
|
||||||
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */,
|
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */,
|
||||||
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */,
|
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */,
|
||||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||||
|
|
|
@ -39,4 +39,12 @@ class Preferences: Codable {
|
||||||
|
|
||||||
var defaultPostVisibility = Status.Visibility.public
|
var defaultPostVisibility = Status.Visibility.public
|
||||||
|
|
||||||
|
var silentActions: [String: Permission] = [:]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Preferences {
|
||||||
|
enum Permission: String, Codable {
|
||||||
|
case undecided, accepted, rejected
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,7 +254,7 @@ class ComposeViewController: UIViewController {
|
||||||
self.performSegue(withIdentifier: "postComplete", sender: self)
|
self.performSegue(withIdentifier: "postComplete", sender: self)
|
||||||
|
|
||||||
self.xcbSession?.complete(with: .success, additionalData: [
|
self.xcbSession?.complete(with: .success, additionalData: [
|
||||||
"statusURL": status.url!.absoluteString,
|
"statusURL": status.url?.absoluteString,
|
||||||
"statusURI": status.uri
|
"statusURI": status.uri
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
//
|
||||||
|
// XCBActions.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/23/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct XCBActions {
|
||||||
|
// MARK: - Posts
|
||||||
|
|
||||||
|
static func postStatus(_ url: XCallbackURL) -> Bool {
|
||||||
|
let session = XCBManager.createSession(type: .postStatus, url: url)
|
||||||
|
|
||||||
|
let mentioning = url.arguments["mentioning"]
|
||||||
|
let text = url.arguments["text"]
|
||||||
|
|
||||||
|
func postStatusSilently() {
|
||||||
|
var status = ""
|
||||||
|
if let mentioning = mentioning { status += mentioning }
|
||||||
|
if let text = text { status += text }
|
||||||
|
let request = MastodonController.shared.client.createStatus(text: status)
|
||||||
|
MastodonController.shared.client.run(request) { response in
|
||||||
|
if case let .success(status, _) = response {
|
||||||
|
session.complete(with: .success, additionalData: [
|
||||||
|
"statusURL": status.url?.absoluteString,
|
||||||
|
"statusURI": status.uri
|
||||||
|
])
|
||||||
|
} else if case let .failure(error) = response {
|
||||||
|
session.complete(with: .error, additionalData: [
|
||||||
|
"error": error.localizedDescription
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showComposeStatus() {
|
||||||
|
let vc = ComposeViewController.create(for: session, mentioning: mentioning, text: text)
|
||||||
|
UIApplication.shared.keyWindow!.rootViewController!.present(vc, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
let silent = Bool(url.arguments["silent"] ?? "false") ?? false
|
||||||
|
if silent {
|
||||||
|
if let source = url.source {
|
||||||
|
let permission = Preferences.shared.silentActions[source] ?? .undecided
|
||||||
|
switch permission {
|
||||||
|
case .accepted:
|
||||||
|
postStatusSilently()
|
||||||
|
case .rejected:
|
||||||
|
showComposeStatus()
|
||||||
|
case .undecided:
|
||||||
|
let alert = UIAlertController(title: "\(source) wants to perform actions silently", message: "Accepting will allow \(source) to perform actions without your confirmation, rejecting will always prompt for confirmation.", preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { (_) in
|
||||||
|
Preferences.shared.silentActions[source] = .accepted
|
||||||
|
postStatusSilently()
|
||||||
|
}))
|
||||||
|
alert.addAction(UIAlertAction(title: "Reject", style: .default, handler: { (_) in
|
||||||
|
Preferences.shared.silentActions[source] = .rejected
|
||||||
|
showComposeStatus()
|
||||||
|
}))
|
||||||
|
UIApplication.shared.keyWindow!.rootViewController!.present(alert, animated: true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
session.complete(with: .error, additionalData: ["error": "Cannot perform silent action without source app, x-source parameter must be specified"])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showComposeStatus()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import UIKit
|
||||||
class XCBManager {
|
class XCBManager {
|
||||||
|
|
||||||
static var specs: [XCallbackURLSpec] = [
|
static var specs: [XCallbackURLSpec] = [
|
||||||
XCallbackURLSpec(path: "/postStatus", arguments: ["mentioning": true, "text": true], handle: postStatus)
|
XCallbackURLSpec(path: "/postStatus", arguments: ["mentioning": true, "text": true, "silent": true], handle: XCBActions.postStatus)
|
||||||
]
|
]
|
||||||
|
|
||||||
static var currentSession: XCBSession?
|
static var currentSession: XCBSession?
|
||||||
|
@ -25,30 +25,10 @@ class XCBManager {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
static func didComplete(session type: XCBSessionType, result: XCBSessionResult) {
|
static func createSession(type: XCBSessionType, url: XCallbackURL) -> XCBSession {
|
||||||
if let currentSession = currentSession,
|
|
||||||
currentSession.type == type {
|
|
||||||
currentSession.complete(with: result)
|
|
||||||
self.currentSession = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func createSession(type: XCBSessionType, url: XCallbackURL) -> XCBSession {
|
|
||||||
let session = XCBSession(type: type, success: url.success, error: url.error, cancel: url.cancel)
|
let session = XCBSession(type: type, success: url.success, error: url.error, cancel: url.cancel)
|
||||||
currentSession = session
|
currentSession = session
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
static func postStatus(_ url: XCallbackURL) -> Bool {
|
|
||||||
let mentioning = url.arguments["mentioning"]
|
|
||||||
let text = url.arguments["text"]
|
|
||||||
if let window = UIApplication.shared.keyWindow {
|
|
||||||
let session = createSession(type: .postStatus, url: url)
|
|
||||||
let vc = ComposeViewController.create(for: session, mentioning: mentioning, text: text)
|
|
||||||
window.rootViewController?.present(vc, animated: true)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,16 +24,19 @@ class XCBSession {
|
||||||
func complete(with result: XCBSessionResult, additionalData: [String: String?]? = nil) {
|
func complete(with result: XCBSessionResult, additionalData: [String: String?]? = nil) {
|
||||||
let url = result == .success ? success : result == .error ? error : cancel
|
let url = result == .success ? success : result == .error ? error : cancel
|
||||||
if var url = url {
|
if var url = url {
|
||||||
|
XCBManager.currentSession = nil
|
||||||
if let additionalData = additionalData {
|
if let additionalData = additionalData {
|
||||||
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
|
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
|
||||||
components.queryItems = components.queryItems ?? []
|
components.queryItems = components.queryItems ?? []
|
||||||
components.queryItems!.append(contentsOf: additionalData.map(URLQueryItem.init))
|
components.queryItems!.append(contentsOf: additionalData.map(URLQueryItem.init))
|
||||||
url = components.url!
|
url = components.url!
|
||||||
}
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
UIApplication.shared.open(url, options: [:])
|
UIApplication.shared.open(url, options: [:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum XCBSessionType {
|
enum XCBSessionType {
|
||||||
case postStatus
|
case postStatus
|
||||||
|
|
|
@ -25,6 +25,7 @@ struct XCallbackURLSpec {
|
||||||
struct XCallbackURL {
|
struct XCallbackURL {
|
||||||
let path: String
|
let path: String
|
||||||
let arguments: [String: String]
|
let arguments: [String: String]
|
||||||
|
let source: String?
|
||||||
let success: URL?
|
let success: URL?
|
||||||
let error: URL?
|
let error: URL?
|
||||||
let cancel: URL?
|
let cancel: URL?
|
||||||
|
@ -37,6 +38,7 @@ struct XCallbackURL {
|
||||||
result[el.key] = value
|
result[el.key] = value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
source = queryItems.first(where: { $0.name == "x-source" }).flatMap { $0.value }
|
||||||
success = queryItems.first(where: { $0.name == "x-success" }).flatMap { $0.value }.flatMap { URL(string: $0) }
|
success = queryItems.first(where: { $0.name == "x-success" }).flatMap { $0.value }.flatMap { URL(string: $0) }
|
||||||
error = queryItems.first(where: { $0.name == "x-error" }).flatMap { $0.value }.flatMap { URL(string: $0) }
|
error = queryItems.first(where: { $0.name == "x-error" }).flatMap { $0.value }.flatMap { URL(string: $0) }
|
||||||
cancel = queryItems.first(where: { $0.name == "x-cancel" }).flatMap { $0.value }.flatMap { URL(string: $0) }
|
cancel = queryItems.first(where: { $0.name == "x-cancel" }).flatMap { $0.value }.flatMap { URL(string: $0) }
|
||||||
|
|
Loading…
Reference in New Issue