diff --git a/apps/macos/VODManager/PRIVACY.md b/apps/macos/VODManager/PRIVACY.md new file mode 100644 index 0000000..4bd77a5 --- /dev/null +++ b/apps/macos/VODManager/PRIVACY.md @@ -0,0 +1,67 @@ +# 隐私政策 + +最后更新日期:2026 年 6 月 30 日 + +感谢您使用 VOD 视频管理器(以下简称"本应用")。本应用尊重并保护您的隐私。 + +## 数据收集 + +本应用 **不会收集、存储或上传任何个人数据**。 + +- 本应用不包含任何分析服务、广告 SDK 或第三方跟踪代码。 +- 本应用不向第三方服务器发送任何用户数据。 +- 本应用无需注册账户即可使用。 + +## 用户输入的数据 + +### 阿里云 AccessKey + +使用本应用时,您需要自行输入阿里云的 AccessKey ID 和 AccessKey Secret。这些信息: + +- **仅存储在您的本地设备上**(通过 SwiftData 本地数据库)。 +- **不会传输到任何第三方服务器**。 +- 仅用于直接访问阿里云视频点播(VOD)OpenAPI。 +- 您可以随时在应用中删除这些信息。 + +### 视频数据 + +本应用从阿里云 VOD 服务获取的视频列表、播放地址等信息: + +- **仅存储在您的本地设备上**。 +- 不会上传到任何第三方服务。 + +## 网络通信 + +本应用仅与 **阿里云 VOD API**(`vod.{region}.aliyuncs.com`)直接通信,用于执行您主动发起的操作,例如: + +- 拉取视频列表和详情 +- 获取播放地址 +- 删除视频 +- 下载视频文件 + +所有网络请求均通过 HTTPS 加密传输。 + +## 数据删除 + +您可以通过以下方式删除所有本地数据: + +- 在应用中删除账号配置。 +- 删除应用的本地数据库文件。 +- 卸载本应用。 + +## 第三方服务 + +本应用使用了以下系统框架和服务: + +- **SwiftData**:本地数据持久化(SQLite) +- **URLSession**:网络请求(仅连接阿里云 API) + +## 隐私政策更新 + +如果本隐私政策发生变更,我们会在本页面更新。继续使用本应用即表示您同意更新后的条款。 + +## 联系我们 + +如有任何关于隐私政策的问题,请通过以下方式联系: + +- GitHub:[https://github.com/anomalyco/Aliyun-VOD-Media-Library-Manager](https://github.com/anomalyco/Aliyun-VOD-Media-Library-Manager) diff --git a/apps/macos/VODManager/VODManager.xcodeproj/project.xcworkspace/xcuserdata/william.xcuserdatad/UserInterfaceState.xcuserstate b/apps/macos/VODManager/VODManager.xcodeproj/project.xcworkspace/xcuserdata/william.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..1000e96 Binary files /dev/null and b/apps/macos/VODManager/VODManager.xcodeproj/project.xcworkspace/xcuserdata/william.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/apps/macos/VODManager/VODManager.xcodeproj/xcuserdata/william.xcuserdatad/xcschemes/xcschememanagement.plist b/apps/macos/VODManager/VODManager.xcodeproj/xcuserdata/william.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..9612f32 --- /dev/null +++ b/apps/macos/VODManager/VODManager.xcodeproj/xcuserdata/william.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + VODManager.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/apps/macos/VODManager/VODManager/Localizable.xcstrings b/apps/macos/VODManager/VODManager/Localizable.xcstrings index aee0399..9f5abae 100644 --- a/apps/macos/VODManager/VODManager/Localizable.xcstrings +++ b/apps/macos/VODManager/VODManager/Localizable.xcstrings @@ -1267,7 +1267,26 @@ } }, "error.no_active_account" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No Active Account" + } + }, + "ug" : { + "stringUnit" : { + "state" : "translated", + "value" : "ھەساب يوق" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "没有激活的账号" + } + } + } }, "error.no_play_url" : { "localizations" : { diff --git a/apps/macos/VODManager/VODManager/Services/VODClient.swift b/apps/macos/VODManager/VODManager/Services/VODClient.swift index 885b20a..55b93a8 100644 --- a/apps/macos/VODManager/VODManager/Services/VODClient.swift +++ b/apps/macos/VODManager/VODManager/Services/VODClient.swift @@ -67,7 +67,7 @@ actor VODClient { let host = endpoint() let query = params.sorted(by: { $0.key < $1.key }) - .map { "\(VODSignature.percentEncodePublic($0.key))=\(VODSignature.percentEncodePublic($0.value))" } + .map { "\(VODSignature.percentEncodeURL($0.key))=\(VODSignature.percentEncodeURL($0.value))" } .joined(separator: "&") let urlString = "https://\(host)/?\(query)" diff --git a/apps/macos/VODManager/VODManager/Services/VODSignature.swift b/apps/macos/VODManager/VODManager/Services/VODSignature.swift index 5a6c771..c61fea9 100644 --- a/apps/macos/VODManager/VODManager/Services/VODSignature.swift +++ b/apps/macos/VODManager/VODManager/Services/VODSignature.swift @@ -43,6 +43,19 @@ struct VODSignature { percentEncode(value) } + static func percentEncodeURL(_ value: String) -> String { + let allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~," + var result = "" + for char in value.utf8 { + if allowed.utf8.contains(char) { + result.append(Character(UnicodeScalar(char))) + } else { + result.append(String(format: "%%%02X", char)) + } + } + return result + } + private static func percentEncode(_ value: String) -> String { let allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~" var result = "" diff --git a/apps/macos/VODManager/VODManager/Views/AccountListView.swift b/apps/macos/VODManager/VODManager/Views/AccountListView.swift index a3d2282..cefb77c 100644 --- a/apps/macos/VODManager/VODManager/Views/AccountListView.swift +++ b/apps/macos/VODManager/VODManager/Views/AccountListView.swift @@ -79,6 +79,7 @@ struct AccountListView: View { .sheet(isPresented: $showAddForm) { AccountFormView(account: nil) } + .navigationTitle("nav.account_config") } } diff --git a/apps/macos/VODManager/VODManager/Views/LogListView.swift b/apps/macos/VODManager/VODManager/Views/LogListView.swift index 47f9010..3b6239c 100644 --- a/apps/macos/VODManager/VODManager/Views/LogListView.swift +++ b/apps/macos/VODManager/VODManager/Views/LogListView.swift @@ -26,5 +26,6 @@ struct LogListView: View { } .padding(.vertical, 4) } + .navigationTitle("nav.operation_logs") } } diff --git a/apps/macos/VODManager/VODManager/Views/VideoListView.swift b/apps/macos/VODManager/VODManager/Views/VideoListView.swift index 332ccf6..5e1d5c5 100644 --- a/apps/macos/VODManager/VODManager/Views/VideoListView.swift +++ b/apps/macos/VODManager/VODManager/Views/VideoListView.swift @@ -20,15 +20,18 @@ struct VideoListView: View { private var totalPages: Int { max(1, (filteredVideos.count + pageSize - 1) / pageSize) } var body: some View { - if accountStore.activeAccount == nil { - ContentUnavailableView( - "error.no_active_account", - systemImage: "person.slash", - description: Text("account.add") - ) - } else { - content + Group { + if accountStore.activeAccount == nil { + ContentUnavailableView { + Label("error.no_active_account", systemImage: "person.slash") + } description: { + Text("account.add") + } + } else { + content + } } + .navigationTitle("nav.video_library") } @ViewBuilder @@ -126,7 +129,8 @@ struct VideoListView: View { TableColumn("video.table.title") { video in HStack { - AsyncImage(url: video.coverURL.flatMap(URL.init)) { image in + let coverURL = video.coverURL?.replacingOccurrences(of: "http://", with: "https://") + AsyncImage(url: coverURL.flatMap(URL.init)) { image in image.resizable().scaledToFill() } placeholder: { Color.gray @@ -192,7 +196,7 @@ struct VideoListView: View { Text("\(pageSizes[i])").tag(i) } } - .frame(width: 80) + .frame(width: 120) .onChange(of: pageSizeIndex) { _, _ in pageNo = 1 updateDisplay()