移植 msext QQShareManager.m:716-771 simpleShareToQQFriend 简化版到 Swift。
QQShare.share 完整实现:
- canOpenURL("mqqapi://") 检测 QQ 安装;未装返回 .notInstalled
- 仅链接分享走完整 URL Scheme(type == "1");type == "2"/其它(截图 /
远端图)暂返回 .success 避免 H5 业务卡住,Phase 4.F 接入
- URL 构造(与 msext 等价):
* scene == .timeline → mqqapi://share/to_qzone? (QQ 空间)
* scene == .friend → mqqapi://share/to_fri? (QQ 好友)
* 参数:version=1 + cflag=0 + req_type=1(无 url 时 req_type=0)+
url=<encoded> + title=<encoded> + description=<encoded>
- encode 函数与 msext QQShareManager.m:1262-1268 等价:仅保留 RFC 3986
unreserved 字符(alphanumeric + "-._~"),其它全部 percent encode
- canOpenURL 二次检查 + await UIApplication.open
- 调起即视为 success(msext 同款乐观策略,URL Scheme 单向无返回)
Info.plist 新增 LSApplicationQueriesSchemes(iOS 9+ canOpenURL 白名单
要求,否则总返回 false):
- mqq / mqqapi / mqqopensdkfriend / mqqopensdkapiV2/V3/V4(QQ 相关)
- snssdk1128(抖音,Phase 4.D 用)
- 微信相关待 Phase 4.E 接入 SDK 时再加(weixin / weixinULAPI 等)
BuildProject 通过
Plan §5 Phase 4.3 勾选
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
93 lines
3.4 KiB
Swift
93 lines
3.4 KiB
Swift
//
|
||
// QQShare.swift
|
||
// ylgamehall
|
||
//
|
||
// QQ 分享 SharePlatform 实现(Phase 4.C 完整)。
|
||
//
|
||
// 移植 msext QQShareManager.m:716-771 simpleShareToQQFriend 的 URL Scheme
|
||
// fallback。**不依赖 QQ SDK**。
|
||
//
|
||
// URL 构造:
|
||
// mqqapi://share/to_fri?version=1&cflag=0&req_type=1
|
||
// &url=<encoded webpageUrl>&title=<encoded title>&description=<encoded desc>
|
||
//
|
||
// encodeString 与 msext line 1262-1268 等价:unreserved 字符集(RFC 3986
|
||
// alphanumeric + "-._~"),其它全部 percent encode。
|
||
//
|
||
// ⚠️ 当前仅支持链接分享(type == "1")。type == "2" 截图 / 其它 远端图
|
||
// 在 Phase 4.F 接入(依赖 UIGraphicsImageRenderer + Photos / 临时文件 +
|
||
// mqqapi://share/to_fri?file_type=img 路径)。
|
||
//
|
||
|
||
import UIKit
|
||
import os.log
|
||
|
||
@MainActor
|
||
public final class QQShare: SharePlatform {
|
||
|
||
public static let shared = QQShare()
|
||
|
||
private static let log = Logger(subsystem: "ylgamehall", category: "QQShare")
|
||
|
||
public let name = "QQ"
|
||
|
||
public var isInstalled: Bool {
|
||
guard let url = URL(string: "mqqapi://") else { return false }
|
||
return UIApplication.shared.canOpenURL(url)
|
||
}
|
||
|
||
public init() {}
|
||
|
||
public func share(_ content: ShareContent, scene: ShareScene) async -> ShareResult {
|
||
guard isInstalled else { return .notInstalled(platform: name) }
|
||
|
||
// 仅链接分享走完整 URL Scheme;图片分享留 Phase 4.F
|
||
guard content.type == "1" else {
|
||
Self.log.warning("QQShare: type=\(content.type, privacy: .public) (非链接) 暂未实现,返回 success 避免 H5 业务卡住")
|
||
return .success
|
||
}
|
||
|
||
// 拼装 URL:mqqapi://share/to_fri? 或 mqqapi://share/to_qzone?
|
||
// scene == .timeline 走 QQ 空间 to_qzone;.friend 走 to_fri(QQ 好友)
|
||
let host = scene == .timeline ? "share/to_qzone" : "share/to_fri"
|
||
var parts: [String] = [
|
||
"version=1",
|
||
"cflag=0",
|
||
"req_type=1"
|
||
]
|
||
if !content.webpageUrl.isEmpty {
|
||
parts.append("url=\(Self.encode(content.webpageUrl))")
|
||
} else {
|
||
// 无 URL → 改 req_type=0 文本分享
|
||
parts = ["version=1", "cflag=0", "req_type=0"]
|
||
}
|
||
if !content.title.isEmpty {
|
||
parts.append("title=\(Self.encode(content.title))")
|
||
}
|
||
if !content.desc.isEmpty {
|
||
parts.append("description=\(Self.encode(content.desc))")
|
||
}
|
||
let urlString = "mqqapi://\(host)?\(parts.joined(separator: "&"))"
|
||
|
||
Self.log.debug("QQShare open: \(urlString, privacy: .public)")
|
||
|
||
guard let qqURL = URL(string: urlString),
|
||
UIApplication.shared.canOpenURL(qqURL)
|
||
else {
|
||
return .failed(reason: "invalid QQ URL")
|
||
}
|
||
|
||
// URL Scheme 单向调起:msext 同款乐观策略 — 调起即视为 success
|
||
let opened = await UIApplication.shared.open(qqURL)
|
||
return opened ? .success : .failed(reason: "openURL failed")
|
||
}
|
||
|
||
/// 与 msext QQShareManager.m:1262-1268 等价:仅保留 RFC 3986 unreserved
|
||
/// 字符(alphanumeric + "-._~"),其它全部 percent encode。
|
||
nonisolated static func encode(_ string: String) -> String {
|
||
var allowed = CharacterSet.alphanumerics
|
||
allowed.insert(charactersIn: "-._~")
|
||
return string.addingPercentEncoding(withAllowedCharacters: allowed) ?? ""
|
||
}
|
||
}
|