Files
youle_app_ios_v2/ylgamehall/Source/SDK/WeChat/WeChatManager.swift
T
joywayerandClaude Opus 4.7 8e4c36d1c8 Phase 4.F:微信截图分享 type=2 / 远端图分享 type=其它
按 msext gameController.m:637-678 字面行为,WechatShare 按 content.type
分发:
- type=1 → shareLink(已有)
- type=2 → captureScreenshot → JPEG 0.6 + thumb 0.4x → shareImage
- type=其它 → downloadRemote(URL) → JPEG 0.9 + thumb 0.4x → shareImage

新增:
- ImageProvider.scaledThumb(_:scale:):等价 msext FuncPublic
  imageByScalingAndCroppingForSize 0.4x 缩放;微信 SDK setThumbImage:
  内部再压到 32KB
- WeChatManager.ShareImage 结构 + shareImage(_:scene:) 异步包装
  (WXImageObject + WXMediaMessage.setThumbImage,FIFO 串行复用 sharePending)

契约:与 type=1 一致,调成功 → sharesuccess 反向 callback(success/cancelled
都发);errCode=-2 → cancelled;其它 errCode → failed。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 01:33:17 +08:00

203 lines
6.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// WeChatManager.swift
// ylgamehall
//
// 微信 SDK 持久 delegate + authorize + share 异步包装。
// 把 WXApiDelegate 的回调 → async/awaitstate UUID 配对多发起方。
//
// Design §8.5msext WXApiManager.m 等价。
//
import Foundation
import UIKit
#if canImport(WechatOpenSDK)
import WechatOpenSDK
#endif
@MainActor
public final class WeChatManager: NSObject {
public static let shared = WeChatManager()
/// 授权 scope(沿用 msext SGDefineInfo.h:104
public static let authScope = "snsapi_message,snsapi_userinfo,snsapi_friend,snsapi_contact"
/// 待 resolve 的 authorize continuationstate UUID 配对)
private var authPending: [String: CheckedContinuation<WXAuthCodePayload, Error>] = [:]
/// 待 resolve 的 share continuationFIFO 串行 — 微信回包不带配对 ID)
private var sharePending: [CheckedContinuation<Void, Error>] = []
public override init() {
super.init()
}
// MARK: - Authorize
/// 拉起微信授权(msext 行为:state = "wechat_sdk" + scope = 4 项)
/// 返回的 code 可用 WeChatAuth.exchangeForUser 拿 7 字段用户信息。
public func authorize() async throws -> WXAuthCodePayload {
#if canImport(WechatOpenSDK)
let state = UUID().uuidString
return try await withCheckedThrowingContinuation { cont in
authPending[state] = cont
let req = SendAuthReq()
req.scope = Self.authScope
req.state = state
WXApi.send(req)
}
#else
throw WeChatError.sdkNotLinked
#endif
}
// MARK: - Share
public enum ShareScene {
case session // 微信好友
case timeline // 朋友圈
}
public struct ShareLink: Sendable {
public let url: String
public let title: String
public let desc: String
public let thumbnailURL: String?
public init(url: String, title: String, desc: String, thumbnailURL: String?) {
self.url = url; self.title = title; self.desc = desc; self.thumbnailURL = thumbnailURL
}
}
/// 图片分享载荷(type=2 截图 / type=3 远端图)。
/// imageData 是大图原始字节(JPEG 等),thumb 是缩略图源 — SDK
/// `setThumbImage:` 内部把 thumb 压到 32KB。
public struct ShareImage {
public let imageData: Data
public let thumb: UIImage
public init(imageData: Data, thumb: UIImage) {
self.imageData = imageData
self.thumb = thumb
}
}
/// 链接分享(FIFO 串行)。微信回包不携带配对 ID,连续调时按入队顺序消费。
public func shareLink(_ link: ShareLink, scene: ShareScene) async throws {
#if canImport(WechatOpenSDK)
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
sharePending.append(cont)
let media = WXWebpageObject()
media.webpageUrl = link.url
let message = WXMediaMessage()
message.title = link.title
message.description = link.desc
message.mediaObject = media
let req = SendMessageToWXReq()
req.bText = false
req.message = message
switch scene {
case .session: req.scene = Int32(WXSceneSession.rawValue)
case .timeline: req.scene = Int32(WXSceneTimeline.rawValue)
}
WXApi.send(req)
}
#else
throw WeChatError.sdkNotLinked
#endif
}
/// 图片分享(FIFO 串行,复用 sharePending)。msext gameController.m:637-678
/// type=2 截图 / type=其它 远端图 共用同一条 WXImageObject + setThumbImage 路径。
public func shareImage(_ img: ShareImage, scene: ShareScene) async throws {
#if canImport(WechatOpenSDK)
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
sharePending.append(cont)
let media = WXImageObject()
media.imageData = img.imageData
let message = WXMediaMessage()
// 图片分享不需要 title / descriptionmsext 字面:传 nil;这里传空串等价)
message.mediaObject = media
message.setThumbImage(img.thumb)
let req = SendMessageToWXReq()
req.bText = false
req.message = message
switch scene {
case .session: req.scene = Int32(WXSceneSession.rawValue)
case .timeline: req.scene = Int32(WXSceneTimeline.rawValue)
}
WXApi.send(req)
}
#else
throw WeChatError.sdkNotLinked
#endif
}
// MARK: - Internal: resolve callbacks
fileprivate func resolveAuth(state: String, code: String?, errCode: Int) {
guard let cont = authPending.removeValue(forKey: state) else { return }
if errCode == 0, let code {
cont.resume(returning: WXAuthCodePayload(code: code, state: state))
} else {
cont.resume(throwing: WeChatError.authFailed(errCode))
}
}
fileprivate func resolveShare(errCode: Int) {
guard !sharePending.isEmpty else { return }
let cont = sharePending.removeFirst()
if errCode == 0 {
cont.resume(returning: ())
} else {
cont.resume(throwing: WeChatError.shareFailed(errCode))
}
}
}
// MARK: - WXApiDelegate
#if canImport(WechatOpenSDK)
extension WeChatManager: WXApiDelegate {
public nonisolated func onReq(_ req: BaseReq) {
// 我们当前只发起请求,不接受外部 req(微信 → 本 App 的 req 用于公众号回复等场景)
}
public nonisolated func onResp(_ resp: BaseResp) {
// 主线程化:BaseResp 由 SDK 在主线程派发,但保留 MainActor.run 兜底
let errCode = Int(resp.errCode)
if let auth = resp as? SendAuthResp {
let code = auth.code
let state = auth.state ?? ""
Task { @MainActor in
Self.shared.resolveAuth(state: state, code: code, errCode: errCode)
}
} else if resp is SendMessageToWXResp {
Task { @MainActor in
Self.shared.resolveShare(errCode: errCode)
}
}
}
}
#endif
// MARK: - Public payload / error types(无 SDK 依赖,可在 canImport 之外使用)
public struct WXAuthCodePayload: Sendable {
public let code: String
public let state: String
}
public enum WeChatError: Error, Sendable {
case sdkNotLinked
case authFailed(Int)
case shareFailed(Int)
}